diff options
| author | Rob Mensching <rob@firegiant.com> | 2021-05-11 07:44:04 -0700 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2021-05-11 07:44:04 -0700 |
| commit | f0c6faa8f16569fced7512c5388ecad5a331d438 (patch) | |
| tree | e2f12ac48e7592d736802fcc218f882aee37df50 /src | |
| parent | 459225b8ad2b11d4cd576f037e459bfb21b5a87a (diff) | |
| parent | 8cf0427984a88b0b3ddfb2061e5be721afffe82e (diff) | |
| download | wix-f0c6faa8f16569fced7512c5388ecad5a331d438.tar.gz wix-f0c6faa8f16569fced7512c5388ecad5a331d438.tar.bz2 wix-f0c6faa8f16569fced7512c5388ecad5a331d438.zip | |
Merge Core
Diffstat (limited to 'src')
610 files changed, 80886 insertions, 0 deletions
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs new file mode 100644 index 00000000..92a9602f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 6 | </PackageGroup> | ||
| 7 | <PayloadGroup Id="OrphanPayloads"> | ||
| 8 | <Payload Id="OrphanPayload" SourceFile="$(sys.SOURCEFILEPATH)" /> | ||
| 9 | </PayloadGroup> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs new file mode 100644 index 00000000..a00874ce --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 6 | </PackageGroup> | ||
| 7 | <Container Id="First"> | ||
| 8 | <PackageGroupRef Id="BundlePackages" /> | ||
| 9 | </Container> | ||
| 10 | <Container Id="Second"> | ||
| 11 | <PackageGroupRef Id="BundlePackages" /> | ||
| 12 | </Container> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs new file mode 100644 index 00000000..ec757c5d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage Id="FirstX86"> | ||
| 6 | <PayloadGroupRef Id="FirstX86Payloads" /> | ||
| 7 | </MsiPackage> | ||
| 8 | <MsiPackage Id="FirstX64" Name="FirstX64\FirstX64.msi" SourceFile="FirstX64\" DownloadUrl="http://example.com/{0}/{1}/{2}" /> | ||
| 9 | </PackageGroup> | ||
| 10 | <Container Id="BundlePackages" Type="attached"> | ||
| 11 | <PackageGroupRef Id="BundlePackages" /> | ||
| 12 | </Container> | ||
| 13 | <PayloadGroup Id="FirstX86Payloads"> | ||
| 14 | <MsiPackagePayload Name="FirstX86\FirstX86.msi" SourceFile="FirstX86\" DownloadUrl="http://example.com/{0}/{1}/{2}" /> | ||
| 15 | </PayloadGroup> | ||
| 16 | </Fragment> | ||
| 17 | </Wix> | ||
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs new file mode 100644 index 00000000..0c5f8c7e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="{B5B23622-239B-4E3B-BDAB-67648CB975BF}"> | ||
| 4 | <BootstrapperApplication> | ||
| 5 | <BootstrapperApplicationDll SourceFile="fakeba.dll" /> | ||
| 6 | </BootstrapperApplication> | ||
| 7 | <Chain> | ||
| 8 | <PackageGroupRef Id="BundlePackages" /> | ||
| 9 | </Chain> | ||
| 10 | <PayloadGroupRef Id="Shared" /> | ||
| 11 | </Bundle> | ||
| 12 | <Fragment> | ||
| 13 | <PackageGroup Id="BundlePackages"> | ||
| 14 | <PackageGroupRef Id="FirstX64" /> | ||
| 15 | </PackageGroup> | ||
| 16 | <PackageGroup Id="FirstX64"> | ||
| 17 | <MsiPackage SourceFile="FirstX64.msi"> | ||
| 18 | <PayloadGroupRef Id="Shared" /> | ||
| 19 | </MsiPackage> | ||
| 20 | </PackageGroup> | ||
| 21 | <Container Id="FirstX64" Name="FirstX64" Type="detached"> | ||
| 22 | <PackageGroupRef Id="FirstX64" /> | ||
| 23 | </Container> | ||
| 24 | <PayloadGroup Id="Shared"> | ||
| 25 | <Payload Id="SharedPayload" SourceFile="$(sys.SOURCEFILEPATH)" /> | ||
| 26 | </PayloadGroup> | ||
| 27 | </Fragment> | ||
| 28 | </Wix> | ||
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs new file mode 100644 index 00000000..c7f549a3 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <PackageGroupRef Id="FirstX86" /> | ||
| 6 | <PackageGroupRef Id="FirstX64" /> | ||
| 7 | </PackageGroup> | ||
| 8 | <PackageGroup Id="FirstX86"> | ||
| 9 | <MsiPackage SourceFile="FirstX86.msi"> | ||
| 10 | <PayloadGroupRef Id="Shared" /> | ||
| 11 | </MsiPackage> | ||
| 12 | </PackageGroup> | ||
| 13 | <PackageGroup Id="FirstX64"> | ||
| 14 | <MsiPackage SourceFile="FirstX64.msi"> | ||
| 15 | <PayloadGroupRef Id="Shared" /> | ||
| 16 | </MsiPackage> | ||
| 17 | </PackageGroup> | ||
| 18 | <Container Id="FirstX86" Name="FirstX86" Type="detached"> | ||
| 19 | <PackageGroupRef Id="FirstX86" /> | ||
| 20 | </Container> | ||
| 21 | <Container Id="FirstX64" Name="FirstX64" Type="detached"> | ||
| 22 | <PackageGroupRef Id="FirstX64" /> | ||
| 23 | </Container> | ||
| 24 | <PayloadGroup Id="Shared"> | ||
| 25 | <Payload Id="SharedPayload" SourceFile="$(sys.SOURCEFILEPATH)" /> | ||
| 26 | </PayloadGroup> | ||
| 27 | </Fragment> | ||
| 28 | </Wix> | ||
diff --git a/src/wix/Custom.Build.props b/src/wix/Custom.Build.props new file mode 100644 index 00000000..889fb62e --- /dev/null +++ b/src/wix/Custom.Build.props | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <!-- 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. --> | ||
| 2 | <Project> | ||
| 3 | <PropertyGroup Condition="'$(Configuration)'=='Release'"> | ||
| 4 | <TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
| 5 | </PropertyGroup> | ||
| 6 | </Project> | ||
diff --git a/src/wix/Directory.Build.props b/src/wix/Directory.Build.props new file mode 100644 index 00000000..b3c6287c --- /dev/null +++ b/src/wix/Directory.Build.props | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | <!-- | ||
| 4 | Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.props | ||
| 5 | then update all of the repos. | ||
| 6 | --> | ||
| 7 | <Project> | ||
| 8 | <PropertyGroup> | ||
| 9 | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
| 10 | <EnableSourceLink Condition=" '$(NCrunch)' == '1' ">false</EnableSourceLink> | ||
| 11 | <MSBuildWarningsAsMessages>MSB3246</MSBuildWarningsAsMessages> | ||
| 12 | |||
| 13 | <ProjectName Condition=" '$(ProjectName)' == '' ">$(MSBuildProjectName)</ProjectName> | ||
| 14 | <BaseOutputPath>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\))</BaseOutputPath> | ||
| 15 | <BaseIntermediateOutputPath>$(BaseOutputPath)obj\$(ProjectName)\</BaseIntermediateOutputPath> | ||
| 16 | <OutputPath>$(BaseOutputPath)$(Configuration)\</OutputPath> | ||
| 17 | |||
| 18 | <Authors>WiX Toolset Team</Authors> | ||
| 19 | <Company>WiX Toolset</Company> | ||
| 20 | <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright> | ||
| 21 | <PackageLicenseExpression>MS-RL</PackageLicenseExpression> | ||
| 22 | <Product>WiX Toolset</Product> | ||
| 23 | </PropertyGroup> | ||
| 24 | |||
| 25 | <Import Project="Directory$(MSBuildProjectExtension).props" Condition=" Exists('Directory$(MSBuildProjectExtension).props') " /> | ||
| 26 | <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " /> | ||
| 27 | </Project> | ||
diff --git a/src/wix/Directory.Build.targets b/src/wix/Directory.Build.targets new file mode 100644 index 00000000..2fcc765a --- /dev/null +++ b/src/wix/Directory.Build.targets | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | <!-- | ||
| 4 | Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.targets | ||
| 5 | then update all of the repos. | ||
| 6 | --> | ||
| 7 | <!-- | ||
| 8 | Replace PackageReferences with ProjectReferences when the projects can be found in .sln. | ||
| 9 | See the original here: https://github.com/dotnet/sdk/issues/1151#issuecomment-385133284 | ||
| 10 | --> | ||
| 11 | <Project> | ||
| 12 | <PropertyGroup> | ||
| 13 | <ReplacePackageReferences>true</ReplacePackageReferences> | ||
| 14 | <TheSolutionPath Condition=" '$(NCrunch)'=='' ">$(SolutionPath)</TheSolutionPath> | ||
| 15 | <TheSolutionPath Condition=" '$(NCrunch)'=='1' ">$(NCrunchOriginalSolutionPath)</TheSolutionPath> | ||
| 16 | </PropertyGroup> | ||
| 17 | |||
| 18 | <Choose> | ||
| 19 | <When Condition="$(ReplacePackageReferences) AND '$(TheSolutionPath)' != '' AND '$(TheSolutionPath)' != '*undefined*' AND Exists('$(TheSolutionPath)')"> | ||
| 20 | |||
| 21 | <PropertyGroup> | ||
| 22 | <SolutionFileContent>$([System.IO.File]::ReadAllText($(TheSolutionPath)))</SolutionFileContent> | ||
| 23 | <SmartSolutionDir>$([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) ))</SmartSolutionDir> | ||
| 24 | <RegexPattern>(?<="[PackageName]", ")(.*)(?=", ")</RegexPattern> | ||
| 25 | </PropertyGroup> | ||
| 26 | |||
| 27 | <ItemGroup> | ||
| 28 | <!-- Keep the identity of the PackageReference --> | ||
| 29 | <SmartPackageReference Include="@(PackageReference)"> | ||
| 30 | <PackageName>%(Identity)</PackageName> | ||
| 31 | <InSolution>$(SolutionFileContent.Contains('\%(Identity).csproj'))</InSolution> | ||
| 32 | </SmartPackageReference> | ||
| 33 | |||
| 34 | <!-- Filter them by mapping them to another ItemGroup using the WithMetadataValue item function --> | ||
| 35 | <PackageInSolution Include="@(SmartPackageReference->WithMetadataValue('InSolution', True))"> | ||
| 36 | <Pattern>$(RegexPattern.Replace('[PackageName]','%(PackageName)') )</Pattern> | ||
| 37 | <SmartPath>$([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)'))</SmartPath> | ||
| 38 | </PackageInSolution> | ||
| 39 | |||
| 40 | <ProjectReference Include="@(PackageInSolution->'$(SmartSolutionDir)\%(SmartPath)' )"/> | ||
| 41 | |||
| 42 | <!-- Remove the package references that are now referenced as projects --> | ||
| 43 | <PackageReference Remove="@(PackageInSolution->'%(PackageName)')"/> | ||
| 44 | </ItemGroup> | ||
| 45 | |||
| 46 | </When> | ||
| 47 | </Choose> | ||
| 48 | |||
| 49 | <Import Project="Directory$(MSBuildProjectExtension).targets" Condition=" Exists('Directory$(MSBuildProjectExtension).targets') " /> | ||
| 50 | <Import Project="Custom.Build.targets" Condition=" Exists('Custom.Build.targets') " /> | ||
| 51 | </Project> | ||
diff --git a/src/wix/Directory.csproj.props b/src/wix/Directory.csproj.props new file mode 100644 index 00000000..81d24ad1 --- /dev/null +++ b/src/wix/Directory.csproj.props | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <!-- 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. --> | ||
| 2 | <!-- | ||
| 3 | Do NOT modify this file. Update the canonical version in Home\repo-template\src\CSharp.Build.props | ||
| 4 | then update all of the repos. | ||
| 5 | --> | ||
| 6 | <Project> | ||
| 7 | <PropertyGroup> | ||
| 8 | <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> | ||
| 9 | <SignAssembly>true</SignAssembly> | ||
| 10 | <AssemblyOriginatorKeyFile>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk))</AssemblyOriginatorKeyFile> | ||
| 11 | <NBGV_EmitThisAssemblyClass>false</NBGV_EmitThisAssemblyClass> | ||
| 12 | </PropertyGroup> | ||
| 13 | </Project> | ||
diff --git a/src/wix/Directory.csproj.targets b/src/wix/Directory.csproj.targets new file mode 100644 index 00000000..c3270426 --- /dev/null +++ b/src/wix/Directory.csproj.targets | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | <!-- 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. --> | ||
| 2 | <!-- | ||
| 3 | Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.csproj.targets | ||
| 4 | then update all of the repos. | ||
| 5 | --> | ||
| 6 | <Project> | ||
| 7 | <PropertyGroup> | ||
| 8 | <CreateDocumentation Condition=" '$(CreateDocumentationFile)'!='true' ">false</CreateDocumentation> | ||
| 9 | <DocumentationFile Condition=" '$(CreateDocumentationFile)'=='true' ">$(OutputPath)\$(AssemblyName).xml</DocumentationFile> | ||
| 10 | </PropertyGroup> | ||
| 11 | |||
| 12 | <Target Name="SetNuspecProperties" DependsOnTargets="InitializeSourceControlInformation" AfterTargets="GetBuildVersion" | ||
| 13 | Condition=" Exists('$(MSBuildProjectName).nuspec') "> | ||
| 14 | <PropertyGroup> | ||
| 15 | <ProjectUrl Condition=" '$(ProjectUrl)'=='' and '$(PrivateRepositoryUrl)'!='' ">$(PrivateRepositoryUrl.Replace('.git',''))</ProjectUrl> | ||
| 16 | |||
| 17 | <NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile> | ||
| 18 | <NuspecBasePath Condition=" '$(NuspecBasePath)'=='' ">$(OutputPath)..\</NuspecBasePath> | ||
| 19 | <NuspecProperties>$(NuspecProperties);Id=$(PackageId);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description);Title=$(Title)</NuspecProperties> | ||
| 20 | <NuspecProperties>$(NuspecProperties);Version=$(PackageVersion);RepositoryCommit=$(SourceRevisionId);RepositoryType=$(RepositoryType);RepositoryUrl=$(PrivateRepositoryUrl);ProjectFolder=$(MSBuildProjectDirectory)\;ProjectUrl=$(ProjectUrl)</NuspecProperties> | ||
| 21 | <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
| 22 | <SymbolPackageFormat>snupkg</SymbolPackageFormat> | ||
| 23 | </PropertyGroup> | ||
| 24 | </Target> | ||
| 25 | |||
| 26 | </Project> | ||
diff --git a/src/wix/README.md b/src/wix/README.md new file mode 100644 index 00000000..622cd3f9 --- /dev/null +++ b/src/wix/README.md | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | # Core | ||
| 2 | WixToolset.Core - preprocessor, compiler, linker and binder for Windows Installer and Burn | ||
| 3 | |||
diff --git a/src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs new file mode 100644 index 00000000..0da78797 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml; | ||
| 7 | using WixToolset.Data.Symbols; | ||
| 8 | |||
| 9 | internal abstract class BaseSearchFacade : ISearchFacade | ||
| 10 | { | ||
| 11 | protected WixSearchSymbol SearchSymbol { get; set; } | ||
| 12 | |||
| 13 | public virtual void WriteXml(XmlTextWriter writer) | ||
| 14 | { | ||
| 15 | writer.WriteAttributeString("Id", this.SearchSymbol.Id.Id); | ||
| 16 | writer.WriteAttributeString("Variable", this.SearchSymbol.Variable); | ||
| 17 | if (!String.IsNullOrEmpty(this.SearchSymbol.Condition)) | ||
| 18 | { | ||
| 19 | writer.WriteAttributeString("Condition", this.SearchSymbol.Condition); | ||
| 20 | } | ||
| 21 | if (!String.IsNullOrEmpty(this.SearchSymbol.BundleExtensionRef)) | ||
| 22 | { | ||
| 23 | writer.WriteAttributeString("ExtensionId", this.SearchSymbol.BundleExtensionRef); | ||
| 24 | } | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs new file mode 100644 index 00000000..4a4f06f3 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | |||
| @@ -0,0 +1,650 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using WixToolset.Core.Burn.Bind; | ||
| 12 | using WixToolset.Core.Burn.Bundles; | ||
| 13 | using WixToolset.Core.Burn.Interfaces; | ||
| 14 | using WixToolset.Data; | ||
| 15 | using WixToolset.Data.Burn; | ||
| 16 | using WixToolset.Data.Symbols; | ||
| 17 | using WixToolset.Extensibility; | ||
| 18 | using WixToolset.Extensibility.Data; | ||
| 19 | using WixToolset.Extensibility.Services; | ||
| 20 | |||
| 21 | /// <summary> | ||
| 22 | /// Binds a this.bundle. | ||
| 23 | /// </summary> | ||
| 24 | internal class BindBundleCommand | ||
| 25 | { | ||
| 26 | public BindBundleCommand(IBindContext context, IEnumerable<IBurnBackendBinderExtension> backedExtensions) | ||
| 27 | { | ||
| 28 | this.ServiceProvider = context.ServiceProvider; | ||
| 29 | |||
| 30 | this.Messaging = context.ServiceProvider.GetService<IMessaging>(); | ||
| 31 | |||
| 32 | this.BackendHelper = context.ServiceProvider.GetService<IBackendHelper>(); | ||
| 33 | this.InternalBurnBackendHelper = context.ServiceProvider.GetService<IInternalBurnBackendHelper>(); | ||
| 34 | this.PayloadHarvester = context.ServiceProvider.GetService<IPayloadHarvester>(); | ||
| 35 | |||
| 36 | this.DefaultCompressionLevel = context.DefaultCompressionLevel; | ||
| 37 | this.DelayedFields = context.DelayedFields; | ||
| 38 | this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; | ||
| 39 | this.IntermediateFolder = context.IntermediateFolder; | ||
| 40 | this.Output = context.IntermediateRepresentation; | ||
| 41 | this.OutputPath = context.OutputPath; | ||
| 42 | this.OutputPdbPath = context.PdbPath; | ||
| 43 | //this.VariableResolver = context.VariableResolver; | ||
| 44 | |||
| 45 | this.BackendExtensions = backedExtensions; | ||
| 46 | } | ||
| 47 | |||
| 48 | private IServiceProvider ServiceProvider { get; } | ||
| 49 | |||
| 50 | private IMessaging Messaging { get; } | ||
| 51 | |||
| 52 | private IBackendHelper BackendHelper { get; } | ||
| 53 | |||
| 54 | private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } | ||
| 55 | |||
| 56 | private IPayloadHarvester PayloadHarvester { get; } | ||
| 57 | |||
| 58 | private CompressionLevel? DefaultCompressionLevel { get; } | ||
| 59 | |||
| 60 | public IEnumerable<IDelayedField> DelayedFields { get; } | ||
| 61 | |||
| 62 | public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; } | ||
| 63 | |||
| 64 | private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; } | ||
| 65 | |||
| 66 | private Intermediate Output { get; } | ||
| 67 | |||
| 68 | private string OutputPath { get; } | ||
| 69 | |||
| 70 | private string OutputPdbPath { get; } | ||
| 71 | |||
| 72 | private string IntermediateFolder { get; } | ||
| 73 | |||
| 74 | private IVariableResolver VariableResolver { get; } | ||
| 75 | |||
| 76 | public IReadOnlyCollection<IFileTransfer> FileTransfers { get; private set; } | ||
| 77 | |||
| 78 | public IReadOnlyCollection<ITrackedFile> TrackedFiles { get; private set; } | ||
| 79 | |||
| 80 | public WixOutput Wixout { get; private set; } | ||
| 81 | |||
| 82 | public void Execute() | ||
| 83 | { | ||
| 84 | var section = this.Output.Sections.Single(); | ||
| 85 | |||
| 86 | var fileTransfers = new List<IFileTransfer>(); | ||
| 87 | var trackedFiles = new List<ITrackedFile>(); | ||
| 88 | |||
| 89 | // First look for data we expect to find... Chain, WixGroups, etc. | ||
| 90 | |||
| 91 | // We shouldn't really get past the linker phase if there are | ||
| 92 | // no group items... that means that there's no UX, no Chain, | ||
| 93 | // *and* no Containers! | ||
| 94 | var chainPackageSymbols = this.GetRequiredSymbols<WixBundlePackageSymbol>(); | ||
| 95 | |||
| 96 | var wixGroupSymbols = this.GetRequiredSymbols<WixGroupSymbol>(); | ||
| 97 | |||
| 98 | // Ensure there is one and only one WixBundleSymbol. | ||
| 99 | // The compiler and linker behavior should have colluded to get | ||
| 100 | // this behavior. | ||
| 101 | var bundleSymbol = this.GetSingleSymbol<WixBundleSymbol>(); | ||
| 102 | |||
| 103 | bundleSymbol.ProviderKey = bundleSymbol.BundleId = Guid.NewGuid().ToString("B").ToUpperInvariant(); | ||
| 104 | |||
| 105 | bundleSymbol.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user. | ||
| 106 | |||
| 107 | // Ensure there is one and only one WixBootstrapperApplicationDllSymbol. | ||
| 108 | // The compiler and linker behavior should have colluded to get | ||
| 109 | // this behavior. | ||
| 110 | var bundleApplicationDllSymbol = this.GetSingleSymbol<WixBootstrapperApplicationDllSymbol>(); | ||
| 111 | |||
| 112 | // Ensure there is one and only one WixChainSymbol. | ||
| 113 | // The compiler and linker behavior should have colluded to get | ||
| 114 | // this behavior. | ||
| 115 | var chainSymbol = this.GetSingleSymbol<WixChainSymbol>(); | ||
| 116 | |||
| 117 | if (this.Messaging.EncounteredError) | ||
| 118 | { | ||
| 119 | return; | ||
| 120 | } | ||
| 121 | |||
| 122 | // If there are any fields to resolve later, create the cache to populate during bind. | ||
| 123 | var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null; | ||
| 124 | |||
| 125 | IEnumerable<ISearchFacade> orderedSearches; | ||
| 126 | IDictionary<string, IEnumerable<IntermediateSymbol>> extensionSearchSymbolsById; | ||
| 127 | { | ||
| 128 | var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); | ||
| 129 | orderSearchesCommand.Execute(); | ||
| 130 | |||
| 131 | orderedSearches = orderSearchesCommand.OrderedSearchFacades; | ||
| 132 | extensionSearchSymbolsById = orderSearchesCommand.ExtensionSearchSymbolsByExtensionId; | ||
| 133 | } | ||
| 134 | |||
| 135 | // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). | ||
| 136 | { | ||
| 137 | var extractedFiles = this.BackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); | ||
| 138 | |||
| 139 | trackedFiles.AddRange(extractedFiles); | ||
| 140 | } | ||
| 141 | |||
| 142 | // Get the explicit payloads. | ||
| 143 | var payloadSymbols = section.Symbols.OfType<WixBundlePayloadSymbol>().ToDictionary(t => t.Id.Id); | ||
| 144 | var packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); | ||
| 145 | |||
| 146 | var layoutDirectory = Path.GetDirectoryName(this.OutputPath); | ||
| 147 | |||
| 148 | // Process the explicitly authored payloads. | ||
| 149 | ISet<string> processedPayloads; | ||
| 150 | { | ||
| 151 | var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); | ||
| 152 | command.Execute(); | ||
| 153 | |||
| 154 | fileTransfers.AddRange(command.FileTransfers); | ||
| 155 | trackedFiles.AddRange(command.TrackedFiles); | ||
| 156 | |||
| 157 | processedPayloads = new HashSet<string>(payloadSymbols.Keys); | ||
| 158 | } | ||
| 159 | |||
| 160 | IDictionary<string, PackageFacade> facades; | ||
| 161 | { | ||
| 162 | var command = new GetPackageFacadesCommand(this.Messaging, chainPackageSymbols, section); | ||
| 163 | command.Execute(); | ||
| 164 | |||
| 165 | facades = command.PackageFacades; | ||
| 166 | } | ||
| 167 | |||
| 168 | if (this.Messaging.EncounteredError) | ||
| 169 | { | ||
| 170 | return; | ||
| 171 | } | ||
| 172 | |||
| 173 | // Process each package facade. Note this is likely to add payloads and other symbols so | ||
| 174 | // note that any indexes created above may be out of date now. | ||
| 175 | foreach (var facade in facades.Values) | ||
| 176 | { | ||
| 177 | switch (facade.PackageSymbol.Type) | ||
| 178 | { | ||
| 179 | case WixBundlePackageType.Exe: | ||
| 180 | { | ||
| 181 | var command = new ProcessExePackageCommand(facade, payloadSymbols); | ||
| 182 | command.Execute(); | ||
| 183 | } | ||
| 184 | break; | ||
| 185 | |||
| 186 | case WixBundlePackageType.Msi: | ||
| 187 | { | ||
| 188 | var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, packagesPayloads[facade.PackageId]); | ||
| 189 | command.Execute(); | ||
| 190 | } | ||
| 191 | break; | ||
| 192 | |||
| 193 | case WixBundlePackageType.Msp: | ||
| 194 | { | ||
| 195 | var command = new ProcessMspPackageCommand(this.Messaging, section, facade, payloadSymbols); | ||
| 196 | command.Execute(); | ||
| 197 | } | ||
| 198 | break; | ||
| 199 | |||
| 200 | case WixBundlePackageType.Msu: | ||
| 201 | { | ||
| 202 | var command = new ProcessMsuPackageCommand(facade, payloadSymbols); | ||
| 203 | command.Execute(); | ||
| 204 | } | ||
| 205 | break; | ||
| 206 | } | ||
| 207 | |||
| 208 | if (null != variableCache) | ||
| 209 | { | ||
| 210 | BindBundleCommand.PopulatePackageVariableCache(facade, variableCache); | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | if (this.Messaging.EncounteredError) | ||
| 215 | { | ||
| 216 | return; | ||
| 217 | } | ||
| 218 | |||
| 219 | // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) | ||
| 220 | // are present. | ||
| 221 | payloadSymbols = section.Symbols.OfType<WixBundlePayloadSymbol>().ToDictionary(t => t.Id.Id); | ||
| 222 | wixGroupSymbols = this.GetRequiredSymbols<WixGroupSymbol>(); | ||
| 223 | packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); | ||
| 224 | |||
| 225 | // Process the payloads that were added by processing the packages. | ||
| 226 | { | ||
| 227 | var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList(); | ||
| 228 | |||
| 229 | var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); | ||
| 230 | command.Execute(); | ||
| 231 | |||
| 232 | fileTransfers.AddRange(command.FileTransfers); | ||
| 233 | trackedFiles.AddRange(command.TrackedFiles); | ||
| 234 | |||
| 235 | processedPayloads = null; | ||
| 236 | } | ||
| 237 | |||
| 238 | // Set the package metadata from the payloads now that we have the complete payload information. | ||
| 239 | { | ||
| 240 | foreach (var facade in facades.Values) | ||
| 241 | { | ||
| 242 | facade.PackageSymbol.Size = 0; | ||
| 243 | |||
| 244 | var packagePayloads = packagesPayloads[facade.PackageId]; | ||
| 245 | |||
| 246 | foreach (var payload in packagePayloads.Values) | ||
| 247 | { | ||
| 248 | facade.PackageSymbol.Size += payload.FileSize.Value; | ||
| 249 | } | ||
| 250 | |||
| 251 | if (!facade.PackageSymbol.InstallSize.HasValue) | ||
| 252 | { | ||
| 253 | facade.PackageSymbol.InstallSize = facade.PackageSymbol.Size; | ||
| 254 | } | ||
| 255 | |||
| 256 | var packagePayload = payloadSymbols[facade.PackageSymbol.PayloadRef]; | ||
| 257 | |||
| 258 | if (String.IsNullOrEmpty(facade.PackageSymbol.Description)) | ||
| 259 | { | ||
| 260 | facade.PackageSymbol.Description = packagePayload.Description; | ||
| 261 | } | ||
| 262 | |||
| 263 | if (String.IsNullOrEmpty(facade.PackageSymbol.DisplayName)) | ||
| 264 | { | ||
| 265 | facade.PackageSymbol.DisplayName = packagePayload.DisplayName; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | // Give the UX payloads their embedded IDs... | ||
| 271 | var uxPayloadIndex = 0; | ||
| 272 | { | ||
| 273 | foreach (var payload in payloadSymbols.Values.Where(p => BurnConstants.BurnUXContainerName == p.ContainerRef)) | ||
| 274 | { | ||
| 275 | // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even | ||
| 276 | // downloaded. The current engine requires the UX to be fully present before any downloading starts, | ||
| 277 | // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads | ||
| 278 | // into the temporary UX directory correctly, so we don't allow external either. | ||
| 279 | if (PackagingType.Embedded != payload.Packaging) | ||
| 280 | { | ||
| 281 | this.Messaging.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.SourceFile.Path)); | ||
| 282 | payload.Packaging = PackagingType.Embedded; | ||
| 283 | } | ||
| 284 | |||
| 285 | payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex); | ||
| 286 | ++uxPayloadIndex; | ||
| 287 | } | ||
| 288 | |||
| 289 | if (0 == uxPayloadIndex) | ||
| 290 | { | ||
| 291 | // If we didn't get any UX payloads, it's an error! | ||
| 292 | throw new WixException(ErrorMessages.MissingBundleInformation("BootstrapperApplication")); | ||
| 293 | } | ||
| 294 | |||
| 295 | // Give the embedded payloads without an embedded id yet an embedded id. | ||
| 296 | var payloadIndex = 0; | ||
| 297 | foreach (var payload in payloadSymbols.Values) | ||
| 298 | { | ||
| 299 | Debug.Assert(PackagingType.Unknown != payload.Packaging); | ||
| 300 | |||
| 301 | if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) | ||
| 302 | { | ||
| 303 | payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAuthoredContainerEmbeddedIdFormat, payloadIndex); | ||
| 304 | ++payloadIndex; | ||
| 305 | } | ||
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | if (this.Messaging.EncounteredError) | ||
| 310 | { | ||
| 311 | return; | ||
| 312 | } | ||
| 313 | |||
| 314 | // Determine patches to automatically slipstream. | ||
| 315 | { | ||
| 316 | var command = new AutomaticallySlipstreamPatchesCommand(section, facades.Values); | ||
| 317 | command.Execute(); | ||
| 318 | } | ||
| 319 | |||
| 320 | if (this.Messaging.EncounteredError) | ||
| 321 | { | ||
| 322 | return; | ||
| 323 | } | ||
| 324 | |||
| 325 | IEnumerable<PackageFacade> orderedFacades; | ||
| 326 | IEnumerable<WixBundleRollbackBoundarySymbol> boundaries; | ||
| 327 | { | ||
| 328 | var command = new OrderPackagesAndRollbackBoundariesCommand(this.Messaging, section, facades); | ||
| 329 | command.Execute(); | ||
| 330 | |||
| 331 | orderedFacades = command.OrderedPackageFacades; | ||
| 332 | boundaries = command.UsedRollbackBoundaries; | ||
| 333 | } | ||
| 334 | |||
| 335 | // Resolve any delayed fields before generating the manifest. | ||
| 336 | if (this.DelayedFields.Any()) | ||
| 337 | { | ||
| 338 | this.BackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); | ||
| 339 | } | ||
| 340 | |||
| 341 | { | ||
| 342 | var command = new ProcessDependencyProvidersCommand(this.Messaging, section, facades); | ||
| 343 | command.Execute(); | ||
| 344 | |||
| 345 | if (!String.IsNullOrEmpty(command.BundleProviderKey)) | ||
| 346 | { | ||
| 347 | bundleSymbol.ProviderKey = command.BundleProviderKey; // set the overridable bundle provider key. | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | // Update the bundle per-machine/per-user scope based on the chained packages. | ||
| 352 | this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades); | ||
| 353 | |||
| 354 | var softwareTags = section.Symbols.OfType<WixBundleTagSymbol>().ToList(); | ||
| 355 | if (softwareTags.Any()) | ||
| 356 | { | ||
| 357 | var command = new ProcessBundleSoftwareTagsCommand(section, softwareTags); | ||
| 358 | command.Execute(); | ||
| 359 | } | ||
| 360 | |||
| 361 | this.DetectDuplicateCacheIds(facades); | ||
| 362 | |||
| 363 | if (this.Messaging.EncounteredError) | ||
| 364 | { | ||
| 365 | return; | ||
| 366 | } | ||
| 367 | |||
| 368 | // Give the extension one last hook before generating the output files. | ||
| 369 | foreach (var extension in this.BackendExtensions) | ||
| 370 | { | ||
| 371 | extension.SymbolsFinalized(section); | ||
| 372 | } | ||
| 373 | |||
| 374 | if (this.Messaging.EncounteredError) | ||
| 375 | { | ||
| 376 | return; | ||
| 377 | } | ||
| 378 | |||
| 379 | // Generate data for all manifests. | ||
| 380 | { | ||
| 381 | var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchSymbolsById); | ||
| 382 | command.Execute(); | ||
| 383 | } | ||
| 384 | |||
| 385 | if (this.Messaging.EncounteredError) | ||
| 386 | { | ||
| 387 | return; | ||
| 388 | } | ||
| 389 | |||
| 390 | // Generate the core-defined BA manifest tables... | ||
| 391 | string baManifestPath; | ||
| 392 | { | ||
| 393 | var command = new CreateBootstrapperApplicationManifestCommand(section, bundleSymbol, orderedFacades, uxPayloadIndex, payloadSymbols, packagesPayloads, this.IntermediateFolder, this.InternalBurnBackendHelper); | ||
| 394 | command.Execute(); | ||
| 395 | |||
| 396 | var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; | ||
| 397 | baManifestPath = command.OutputPath; | ||
| 398 | payloadSymbols.Add(baManifestPayload.Id.Id, baManifestPayload); | ||
| 399 | ++uxPayloadIndex; | ||
| 400 | |||
| 401 | trackedFiles.Add(this.BackendHelper.TrackFile(baManifestPath, TrackedFileType.Temporary)); | ||
| 402 | } | ||
| 403 | |||
| 404 | // Generate the bundle extension manifest... | ||
| 405 | string bextManifestPath; | ||
| 406 | { | ||
| 407 | var command = new CreateBundleExtensionManifestCommand(section, bundleSymbol, uxPayloadIndex, this.IntermediateFolder, this.InternalBurnBackendHelper); | ||
| 408 | command.Execute(); | ||
| 409 | |||
| 410 | var bextManifestPayload = command.BundleExtensionManifestPayloadRow; | ||
| 411 | bextManifestPath = command.OutputPath; | ||
| 412 | payloadSymbols.Add(bextManifestPayload.Id.Id, bextManifestPayload); | ||
| 413 | ++uxPayloadIndex; | ||
| 414 | |||
| 415 | trackedFiles.Add(this.BackendHelper.TrackFile(bextManifestPath, TrackedFileType.Temporary)); | ||
| 416 | } | ||
| 417 | |||
| 418 | var containers = section.Symbols.OfType<WixBundleContainerSymbol>().ToDictionary(t => t.Id.Id); | ||
| 419 | { | ||
| 420 | var command = new DetectPayloadCollisionsCommand(this.Messaging, containers, facades.Values, payloadSymbols, packagesPayloads); | ||
| 421 | command.Execute(); | ||
| 422 | } | ||
| 423 | |||
| 424 | if (this.Messaging.EncounteredError) | ||
| 425 | { | ||
| 426 | return; | ||
| 427 | } | ||
| 428 | |||
| 429 | // Create all the containers except the UX container first so the manifest (that goes in the UX container) | ||
| 430 | // can contain all size and hash information about the non-UX containers. | ||
| 431 | WixBundleContainerSymbol uxContainer; | ||
| 432 | IEnumerable<WixBundlePayloadSymbol> uxPayloads; | ||
| 433 | { | ||
| 434 | var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); | ||
| 435 | command.Execute(); | ||
| 436 | |||
| 437 | fileTransfers.AddRange(command.FileTransfers); | ||
| 438 | trackedFiles.AddRange(command.TrackedFiles); | ||
| 439 | |||
| 440 | uxContainer = command.UXContainer; | ||
| 441 | uxPayloads = command.UXContainerPayloads; | ||
| 442 | } | ||
| 443 | |||
| 444 | if (this.Messaging.EncounteredError) | ||
| 445 | { | ||
| 446 | return; | ||
| 447 | } | ||
| 448 | |||
| 449 | // Resolve the download URLs now that we have all of the containers and payloads calculated. | ||
| 450 | { | ||
| 451 | var command = new ResolveDownloadUrlsCommand(this.Messaging, this.BackendExtensions, containers.Values, payloadSymbols); | ||
| 452 | command.Execute(); | ||
| 453 | } | ||
| 454 | |||
| 455 | // Create the bundle manifest. | ||
| 456 | string manifestPath; | ||
| 457 | { | ||
| 458 | var executableName = Path.GetFileName(this.OutputPath); | ||
| 459 | |||
| 460 | var command = new CreateBurnManifestCommand(executableName, section, bundleSymbol, containers.Values, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, packagesPayloads, orderedSearches, this.IntermediateFolder); | ||
| 461 | command.Execute(); | ||
| 462 | |||
| 463 | manifestPath = command.OutputPath; | ||
| 464 | trackedFiles.Add(this.BackendHelper.TrackFile(manifestPath, TrackedFileType.Temporary)); | ||
| 465 | } | ||
| 466 | |||
| 467 | // Create the UX container. | ||
| 468 | { | ||
| 469 | var command = new CreateContainerCommand(manifestPath, uxPayloads, uxContainer.WorkingPath, this.DefaultCompressionLevel); | ||
| 470 | command.Execute(); | ||
| 471 | |||
| 472 | uxContainer.Hash = command.Hash; | ||
| 473 | uxContainer.Size = command.Size; | ||
| 474 | |||
| 475 | trackedFiles.Add(this.BackendHelper.TrackFile(uxContainer.WorkingPath, TrackedFileType.Temporary)); | ||
| 476 | } | ||
| 477 | |||
| 478 | { | ||
| 479 | var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers.Values); | ||
| 480 | command.Execute(); | ||
| 481 | |||
| 482 | fileTransfers.Add(command.Transfer); | ||
| 483 | trackedFiles.Add(this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final)); | ||
| 484 | } | ||
| 485 | |||
| 486 | #if TODO // does this need to come back, or do they only need to be in TrackedFiles? | ||
| 487 | this.ContentFilePaths = payloadSymbols.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); | ||
| 488 | #endif | ||
| 489 | this.FileTransfers = fileTransfers; | ||
| 490 | this.TrackedFiles = trackedFiles; | ||
| 491 | this.Wixout = this.CreateWixout(trackedFiles, this.Output, manifestPath, baManifestPath, bextManifestPath); | ||
| 492 | } | ||
| 493 | |||
| 494 | private WixOutput CreateWixout(List<ITrackedFile> trackedFiles, Intermediate intermediate, string manifestPath, string baDataPath, string bextDataPath) | ||
| 495 | { | ||
| 496 | WixOutput wixout; | ||
| 497 | |||
| 498 | if (String.IsNullOrEmpty(this.OutputPdbPath)) | ||
| 499 | { | ||
| 500 | wixout = WixOutput.Create(); | ||
| 501 | } | ||
| 502 | else | ||
| 503 | { | ||
| 504 | var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); | ||
| 505 | trackedFiles.Add(trackPdb); | ||
| 506 | |||
| 507 | wixout = WixOutput.Create(trackPdb.Path); | ||
| 508 | } | ||
| 509 | |||
| 510 | intermediate.Save(wixout); | ||
| 511 | |||
| 512 | wixout.ImportDataStream(BurnConstants.BurnManifestWixOutputStreamName, manifestPath); | ||
| 513 | wixout.ImportDataStream(BurnConstants.BootstrapperApplicationDataWixOutputStreamName, baDataPath); | ||
| 514 | wixout.ImportDataStream(BurnConstants.BundleExtensionDataWixOutputStreamName, bextDataPath); | ||
| 515 | |||
| 516 | wixout.Reopen(); | ||
| 517 | |||
| 518 | return wixout; | ||
| 519 | } | ||
| 520 | |||
| 521 | /// <summary> | ||
| 522 | /// Populates the variable cache with specific package properties. | ||
| 523 | /// </summary> | ||
| 524 | /// <param name="facade">The package facade with properties to cache.</param> | ||
| 525 | /// <param name="variableCache">The property cache.</param> | ||
| 526 | private static void PopulatePackageVariableCache(PackageFacade facade, IDictionary<string, string> variableCache) | ||
| 527 | { | ||
| 528 | var package = facade.PackageSymbol; | ||
| 529 | var id = package.Id.Id; | ||
| 530 | |||
| 531 | variableCache.Add(String.Concat("packageDescription.", id), package.Description ?? String.Empty); | ||
| 532 | variableCache.Add(String.Concat("packageName.", id), package.DisplayName ?? String.Empty); | ||
| 533 | variableCache.Add(String.Concat("packageVersion.", id), package.Version); | ||
| 534 | |||
| 535 | if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) | ||
| 536 | { | ||
| 537 | variableCache.Add(String.Concat("packageLanguage.", id), msiPackage.ProductLanguage.ToString()); | ||
| 538 | variableCache.Add(String.Concat("packageManufacturer.", id), msiPackage.Manufacturer ?? String.Empty); | ||
| 539 | } | ||
| 540 | else | ||
| 541 | { | ||
| 542 | variableCache.Add(String.Concat("packageLanguage.", id), String.Empty); | ||
| 543 | variableCache.Add(String.Concat("packageManufacturer.", id), String.Empty); | ||
| 544 | } | ||
| 545 | } | ||
| 546 | |||
| 547 | private void ResolveBundleInstallScope(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable<PackageFacade> facades) | ||
| 548 | { | ||
| 549 | var dependencySymbolsById = section.Symbols.OfType<WixDependencyProviderSymbol>().ToDictionary(t => t.Id.Id); | ||
| 550 | |||
| 551 | foreach (var facade in facades) | ||
| 552 | { | ||
| 553 | if (bundleSymbol.PerMachine && YesNoDefaultType.No == facade.PackageSymbol.PerMachine) | ||
| 554 | { | ||
| 555 | this.Messaging.Write(VerboseMessages.SwitchingToPerUserPackage(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); | ||
| 556 | |||
| 557 | bundleSymbol.Attributes &= ~WixBundleAttributes.PerMachine; | ||
| 558 | break; | ||
| 559 | } | ||
| 560 | } | ||
| 561 | |||
| 562 | foreach (var facade in facades) | ||
| 563 | { | ||
| 564 | // Update package scope from bundle scope if default. | ||
| 565 | if (YesNoDefaultType.Default == facade.PackageSymbol.PerMachine) | ||
| 566 | { | ||
| 567 | facade.PackageSymbol.PerMachine = bundleSymbol.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; | ||
| 568 | } | ||
| 569 | |||
| 570 | // We will only register packages in the same scope as the bundle. Warn if any packages with providers | ||
| 571 | // are in a different scope and not permanent (permanents typically don't need a ref-count). | ||
| 572 | if (!bundleSymbol.PerMachine && | ||
| 573 | YesNoDefaultType.Yes == facade.PackageSymbol.PerMachine && | ||
| 574 | !facade.PackageSymbol.Permanent && | ||
| 575 | dependencySymbolsById.ContainsKey(facade.PackageId)) | ||
| 576 | { | ||
| 577 | this.Messaging.Write(WarningMessages.NoPerMachineDependencies(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); | ||
| 578 | } | ||
| 579 | } | ||
| 580 | } | ||
| 581 | |||
| 582 | private void DetectDuplicateCacheIds(IDictionary<string, PackageFacade> facades) | ||
| 583 | { | ||
| 584 | var duplicateCacheIdDetector = new Dictionary<string, WixBundlePackageSymbol>(); | ||
| 585 | |||
| 586 | foreach (var facade in facades.Values) | ||
| 587 | { | ||
| 588 | if (duplicateCacheIdDetector.TryGetValue(facade.PackageSymbol.CacheId, out var collisionPackage)) | ||
| 589 | { | ||
| 590 | this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.CacheId, facade.PackageId)); | ||
| 591 | this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds2(collisionPackage.SourceLineNumbers)); | ||
| 592 | } | ||
| 593 | else | ||
| 594 | { | ||
| 595 | duplicateCacheIdDetector.Add(facade.PackageSymbol.CacheId, facade.PackageSymbol); | ||
| 596 | } | ||
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 | private IEnumerable<T> GetRequiredSymbols<T>() where T : IntermediateSymbol | ||
| 601 | { | ||
| 602 | var symbols = this.Output.Sections.Single().Symbols.OfType<T>().ToList(); | ||
| 603 | |||
| 604 | if (0 == symbols.Count) | ||
| 605 | { | ||
| 606 | throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); | ||
| 607 | } | ||
| 608 | |||
| 609 | return symbols; | ||
| 610 | } | ||
| 611 | |||
| 612 | private T GetSingleSymbol<T>() where T : IntermediateSymbol | ||
| 613 | { | ||
| 614 | var symbols = this.Output.Sections.Single().Symbols.OfType<T>().ToList(); | ||
| 615 | |||
| 616 | if (1 != symbols.Count) | ||
| 617 | { | ||
| 618 | throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); | ||
| 619 | } | ||
| 620 | |||
| 621 | return symbols[0]; | ||
| 622 | } | ||
| 623 | |||
| 624 | private static Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> RecalculatePackagesPayloads(Dictionary<string, WixBundlePayloadSymbol> payloadSymbols, IEnumerable<WixGroupSymbol> wixGroupSymbols) | ||
| 625 | { | ||
| 626 | var packagesPayloads = new Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>>(); | ||
| 627 | |||
| 628 | foreach (var groupSymbol in wixGroupSymbols) | ||
| 629 | { | ||
| 630 | if (ComplexReferenceChildType.Payload == groupSymbol.ChildType) | ||
| 631 | { | ||
| 632 | var payloadSymbol = payloadSymbols[groupSymbol.ChildId]; | ||
| 633 | |||
| 634 | if (ComplexReferenceParentType.Package == groupSymbol.ParentType) | ||
| 635 | { | ||
| 636 | if (!packagesPayloads.TryGetValue(groupSymbol.ParentId, out var packagePayloadsById)) | ||
| 637 | { | ||
| 638 | packagePayloadsById = new Dictionary<string, WixBundlePayloadSymbol>(); | ||
| 639 | packagesPayloads.Add(groupSymbol.ParentId, packagePayloadsById); | ||
| 640 | } | ||
| 641 | |||
| 642 | packagePayloadsById.Add(payloadSymbol.Id.Id, payloadSymbol); | ||
| 643 | } | ||
| 644 | } | ||
| 645 | } | ||
| 646 | |||
| 647 | return packagesPayloads; | ||
| 648 | } | ||
| 649 | } | ||
| 650 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs new file mode 100644 index 00000000..773250d7 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System.Xml; | ||
| 6 | using WixToolset.Data.Symbols; | ||
| 7 | |||
| 8 | internal class ExtensionSearchFacade : BaseSearchFacade | ||
| 9 | { | ||
| 10 | public ExtensionSearchFacade(WixSearchSymbol searchSymbol) | ||
| 11 | { | ||
| 12 | this.SearchSymbol = searchSymbol; | ||
| 13 | } | ||
| 14 | |||
| 15 | public override void WriteXml(XmlTextWriter writer) | ||
| 16 | { | ||
| 17 | writer.WriteStartElement("ExtensionSearch"); | ||
| 18 | |||
| 19 | base.WriteXml(writer); | ||
| 20 | |||
| 21 | writer.WriteEndElement(); | ||
| 22 | } | ||
| 23 | } | ||
| 24 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs new file mode 100644 index 00000000..a76f84ec --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs | |||
| @@ -0,0 +1,237 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using System.Text; | ||
| 9 | using System.Xml; | ||
| 10 | using WixToolset.Core.Burn.Bundles; | ||
| 11 | using WixToolset.Core.Burn.ExtensibilityServices; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Extensibility; | ||
| 15 | using WixToolset.Extensibility.Services; | ||
| 16 | |||
| 17 | internal class GenerateManifestDataFromIRCommand | ||
| 18 | { | ||
| 19 | public GenerateManifestDataFromIRCommand(IMessaging messaging, IntermediateSection section, IEnumerable<IBurnBackendBinderExtension> backendExtensions, IBurnBackendHelper backendHelper, IDictionary<string, IEnumerable<IntermediateSymbol>> extensionSearchSymbolsById) | ||
| 20 | { | ||
| 21 | this.Messaging = messaging; | ||
| 22 | this.Section = section; | ||
| 23 | this.BackendExtensions = backendExtensions; | ||
| 24 | this.BackendHelper = backendHelper; | ||
| 25 | this.ExtensionSearchSymbolsById = extensionSearchSymbolsById; | ||
| 26 | } | ||
| 27 | |||
| 28 | private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; } | ||
| 29 | |||
| 30 | private IBurnBackendHelper BackendHelper { get; } | ||
| 31 | |||
| 32 | private IDictionary<string, IEnumerable<IntermediateSymbol>> ExtensionSearchSymbolsById { get; } | ||
| 33 | |||
| 34 | private IMessaging Messaging { get; } | ||
| 35 | |||
| 36 | private IntermediateSection Section { get; } | ||
| 37 | |||
| 38 | public void Execute() | ||
| 39 | { | ||
| 40 | var symbols = this.Section.Symbols.ToList(); | ||
| 41 | var cellsByCustomDataAndElementId = new Dictionary<string, List<WixBundleCustomDataCellSymbol>>(); | ||
| 42 | var customDataById = new Dictionary<string, WixBundleCustomDataSymbol>(); | ||
| 43 | |||
| 44 | foreach (var kvp in this.ExtensionSearchSymbolsById) | ||
| 45 | { | ||
| 46 | var extensionId = kvp.Key; | ||
| 47 | var extensionSearchSymbols = kvp.Value; | ||
| 48 | foreach (var extensionSearchSymbol in extensionSearchSymbols) | ||
| 49 | { | ||
| 50 | this.BackendHelper.AddBundleExtensionData(extensionId, extensionSearchSymbol, symbolIdIsIdAttribute: true); | ||
| 51 | symbols.Remove(extensionSearchSymbol); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | foreach (var symbol in symbols) | ||
| 56 | { | ||
| 57 | var unknownSymbol = false; | ||
| 58 | switch (symbol.Definition.Type) | ||
| 59 | { | ||
| 60 | // Symbols used internally and are not added to a data manifest. | ||
| 61 | case SymbolDefinitionType.ProvidesDependency: | ||
| 62 | case SymbolDefinitionType.WixApprovedExeForElevation: | ||
| 63 | case SymbolDefinitionType.WixBootstrapperApplication: | ||
| 64 | case SymbolDefinitionType.WixBootstrapperApplicationDll: | ||
| 65 | case SymbolDefinitionType.WixBundle: | ||
| 66 | case SymbolDefinitionType.WixBundleContainer: | ||
| 67 | case SymbolDefinitionType.WixBundleCustomDataAttribute: | ||
| 68 | case SymbolDefinitionType.WixBundleExePackage: | ||
| 69 | case SymbolDefinitionType.WixBundleExePackagePayload: | ||
| 70 | case SymbolDefinitionType.WixBundleExtension: | ||
| 71 | case SymbolDefinitionType.WixBundleMsiFeature: | ||
| 72 | case SymbolDefinitionType.WixBundleMsiPackage: | ||
| 73 | case SymbolDefinitionType.WixBundleMsiPackagePayload: | ||
| 74 | case SymbolDefinitionType.WixBundleMsiProperty: | ||
| 75 | case SymbolDefinitionType.WixBundleMspPackage: | ||
| 76 | case SymbolDefinitionType.WixBundleMspPackagePayload: | ||
| 77 | case SymbolDefinitionType.WixBundleMsuPackage: | ||
| 78 | case SymbolDefinitionType.WixBundleMsuPackagePayload: | ||
| 79 | case SymbolDefinitionType.WixBundlePackage: | ||
| 80 | case SymbolDefinitionType.WixBundlePackageCommandLine: | ||
| 81 | case SymbolDefinitionType.WixBundlePackageExitCode: | ||
| 82 | case SymbolDefinitionType.WixBundlePackageGroup: | ||
| 83 | case SymbolDefinitionType.WixBundlePatchTargetCode: | ||
| 84 | case SymbolDefinitionType.WixBundlePayload: | ||
| 85 | case SymbolDefinitionType.WixBundlePayloadGroup: | ||
| 86 | case SymbolDefinitionType.WixBundleRelatedPackage: | ||
| 87 | case SymbolDefinitionType.WixBundleRollbackBoundary: | ||
| 88 | case SymbolDefinitionType.WixBundleSlipstreamMsp: | ||
| 89 | case SymbolDefinitionType.WixBundleTag: | ||
| 90 | case SymbolDefinitionType.WixBundleUpdate: | ||
| 91 | case SymbolDefinitionType.WixBundleVariable: | ||
| 92 | case SymbolDefinitionType.WixBuildInfo: | ||
| 93 | case SymbolDefinitionType.WixChain: | ||
| 94 | case SymbolDefinitionType.WixComponentSearch: | ||
| 95 | case SymbolDefinitionType.WixDependencyProvider: | ||
| 96 | case SymbolDefinitionType.WixFileSearch: | ||
| 97 | case SymbolDefinitionType.WixGroup: | ||
| 98 | case SymbolDefinitionType.WixProductSearch: | ||
| 99 | case SymbolDefinitionType.WixRegistrySearch: | ||
| 100 | case SymbolDefinitionType.WixRelatedBundle: | ||
| 101 | case SymbolDefinitionType.WixSearch: | ||
| 102 | case SymbolDefinitionType.WixSearchRelation: | ||
| 103 | case SymbolDefinitionType.WixSetVariable: | ||
| 104 | case SymbolDefinitionType.WixUpdateRegistration: | ||
| 105 | break; | ||
| 106 | |||
| 107 | // Symbols used before binding. | ||
| 108 | case SymbolDefinitionType.WixComplexReference: | ||
| 109 | case SymbolDefinitionType.WixOrdering: | ||
| 110 | case SymbolDefinitionType.WixSimpleReference: | ||
| 111 | case SymbolDefinitionType.WixVariable: | ||
| 112 | break; | ||
| 113 | |||
| 114 | // Symbols to investigate: | ||
| 115 | case SymbolDefinitionType.WixChainItem: | ||
| 116 | break; | ||
| 117 | |||
| 118 | case SymbolDefinitionType.WixBundleCustomData: | ||
| 119 | unknownSymbol = !this.IndexBundleCustomDataSymbol((WixBundleCustomDataSymbol)symbol, customDataById); | ||
| 120 | break; | ||
| 121 | |||
| 122 | case SymbolDefinitionType.WixBundleCustomDataCell: | ||
| 123 | this.IndexBundleCustomDataCellSymbol((WixBundleCustomDataCellSymbol)symbol, cellsByCustomDataAndElementId); | ||
| 124 | break; | ||
| 125 | |||
| 126 | case SymbolDefinitionType.MustBeFromAnExtension: | ||
| 127 | unknownSymbol = !this.AddSymbolFromExtension(symbol); | ||
| 128 | break; | ||
| 129 | |||
| 130 | default: | ||
| 131 | unknownSymbol = true; | ||
| 132 | break; | ||
| 133 | } | ||
| 134 | |||
| 135 | if (unknownSymbol) | ||
| 136 | { | ||
| 137 | this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | this.AddIndexedCellSymbols(customDataById, cellsByCustomDataAndElementId); | ||
| 142 | } | ||
| 143 | |||
| 144 | private bool IndexBundleCustomDataSymbol(WixBundleCustomDataSymbol wixBundleCustomDataSymbol, Dictionary<string, WixBundleCustomDataSymbol> customDataById) | ||
| 145 | { | ||
| 146 | switch (wixBundleCustomDataSymbol.Type) | ||
| 147 | { | ||
| 148 | case WixBundleCustomDataType.BootstrapperApplication: | ||
| 149 | case WixBundleCustomDataType.BundleExtension: | ||
| 150 | break; | ||
| 151 | default: | ||
| 152 | return false; | ||
| 153 | } | ||
| 154 | |||
| 155 | var customDataId = wixBundleCustomDataSymbol.Id.Id; | ||
| 156 | customDataById.Add(customDataId, wixBundleCustomDataSymbol); | ||
| 157 | return true; | ||
| 158 | } | ||
| 159 | |||
| 160 | private void IndexBundleCustomDataCellSymbol(WixBundleCustomDataCellSymbol wixBundleCustomDataCellSymbol, Dictionary<string, List<WixBundleCustomDataCellSymbol>> cellsByCustomDataAndElementId) | ||
| 161 | { | ||
| 162 | var tableAndRowId = wixBundleCustomDataCellSymbol.CustomDataRef + "/" + wixBundleCustomDataCellSymbol.ElementId; | ||
| 163 | if (!cellsByCustomDataAndElementId.TryGetValue(tableAndRowId, out var cells)) | ||
| 164 | { | ||
| 165 | cells = new List<WixBundleCustomDataCellSymbol>(); | ||
| 166 | cellsByCustomDataAndElementId.Add(tableAndRowId, cells); | ||
| 167 | } | ||
| 168 | |||
| 169 | cells.Add(wixBundleCustomDataCellSymbol); | ||
| 170 | } | ||
| 171 | |||
| 172 | private void AddIndexedCellSymbols(Dictionary<string, WixBundleCustomDataSymbol> customDataById, Dictionary<string, List<WixBundleCustomDataCellSymbol>> cellsByCustomDataAndElementId) | ||
| 173 | { | ||
| 174 | foreach (var elementValues in cellsByCustomDataAndElementId.Values) | ||
| 175 | { | ||
| 176 | var elementName = elementValues[0].CustomDataRef; | ||
| 177 | var customDataSymbol = customDataById[elementName]; | ||
| 178 | |||
| 179 | var attributeNames = customDataSymbol.AttributeNamesSeparated; | ||
| 180 | |||
| 181 | var elementValuesByAttribute = elementValues.ToDictionary(t => t.AttributeRef, t => t.Value); | ||
| 182 | |||
| 183 | var sb = new StringBuilder(); | ||
| 184 | using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings)) | ||
| 185 | { | ||
| 186 | switch (customDataSymbol.Type) | ||
| 187 | { | ||
| 188 | case WixBundleCustomDataType.BootstrapperApplication: | ||
| 189 | writer.WriteStartElement(elementName, BurnCommon.BADataNamespace); | ||
| 190 | break; | ||
| 191 | case WixBundleCustomDataType.BundleExtension: | ||
| 192 | writer.WriteStartElement(elementName, BurnCommon.BundleExtensionDataNamespace); | ||
| 193 | break; | ||
| 194 | default: | ||
| 195 | throw new NotImplementedException(); | ||
| 196 | } | ||
| 197 | |||
| 198 | // Write all row data as attributes in table column order. | ||
| 199 | foreach (var attributeName in attributeNames) | ||
| 200 | { | ||
| 201 | if (elementValuesByAttribute.TryGetValue(attributeName, out var value)) | ||
| 202 | { | ||
| 203 | writer.WriteAttributeString(attributeName, value); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | writer.WriteEndElement(); | ||
| 208 | } | ||
| 209 | |||
| 210 | switch (customDataSymbol.Type) | ||
| 211 | { | ||
| 212 | case WixBundleCustomDataType.BootstrapperApplication: | ||
| 213 | this.BackendHelper.AddBootstrapperApplicationData(sb.ToString()); | ||
| 214 | break; | ||
| 215 | case WixBundleCustomDataType.BundleExtension: | ||
| 216 | this.BackendHelper.AddBundleExtensionData(customDataSymbol.BundleExtensionRef, sb.ToString()); | ||
| 217 | break; | ||
| 218 | default: | ||
| 219 | throw new NotImplementedException(); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | private bool AddSymbolFromExtension(IntermediateSymbol symbol) | ||
| 225 | { | ||
| 226 | foreach (var extension in this.BackendExtensions) | ||
| 227 | { | ||
| 228 | if (extension.TryProcessSymbol(this.Section, symbol)) | ||
| 229 | { | ||
| 230 | return true; | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | return false; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs new file mode 100644 index 00000000..24d6f542 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs | |||
| @@ -0,0 +1,185 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Symbols; | ||
| 9 | |||
| 10 | internal class LegacySearchFacade : BaseSearchFacade | ||
| 11 | { | ||
| 12 | public LegacySearchFacade(WixSearchSymbol searchSymbol, IntermediateSymbol searchSpecificSymbol) | ||
| 13 | { | ||
| 14 | this.SearchSymbol = searchSymbol; | ||
| 15 | this.SearchSpecificSymbol = searchSpecificSymbol; | ||
| 16 | } | ||
| 17 | |||
| 18 | public IntermediateSymbol SearchSpecificSymbol { get; } | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Generates Burn manifest and ParameterInfo-style markup a search. | ||
| 22 | /// </summary> | ||
| 23 | /// <param name="writer"></param> | ||
| 24 | public override void WriteXml(XmlTextWriter writer) | ||
| 25 | { | ||
| 26 | switch (this.SearchSpecificSymbol) | ||
| 27 | { | ||
| 28 | case WixComponentSearchSymbol symbol: | ||
| 29 | this.WriteComponentSearchXml(writer, symbol); | ||
| 30 | break; | ||
| 31 | case WixFileSearchSymbol symbol: | ||
| 32 | this.WriteFileSearchXml(writer, symbol); | ||
| 33 | break; | ||
| 34 | case WixProductSearchSymbol symbol: | ||
| 35 | this.WriteProductSearchXml(writer, symbol); | ||
| 36 | break; | ||
| 37 | case WixRegistrySearchSymbol symbol: | ||
| 38 | this.WriteRegistrySearchXml(writer, symbol); | ||
| 39 | break; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchSymbol searchSymbol) | ||
| 44 | { | ||
| 45 | writer.WriteStartElement("MsiComponentSearch"); | ||
| 46 | |||
| 47 | base.WriteXml(writer); | ||
| 48 | |||
| 49 | writer.WriteAttributeString("ComponentId", searchSymbol.Guid); | ||
| 50 | |||
| 51 | if (!String.IsNullOrEmpty(searchSymbol.ProductCode)) | ||
| 52 | { | ||
| 53 | writer.WriteAttributeString("ProductCode", searchSymbol.ProductCode); | ||
| 54 | } | ||
| 55 | |||
| 56 | if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.KeyPath)) | ||
| 57 | { | ||
| 58 | writer.WriteAttributeString("Type", "keyPath"); | ||
| 59 | } | ||
| 60 | else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.State)) | ||
| 61 | { | ||
| 62 | writer.WriteAttributeString("Type", "state"); | ||
| 63 | } | ||
| 64 | else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.WantDirectory)) | ||
| 65 | { | ||
| 66 | writer.WriteAttributeString("Type", "directory"); | ||
| 67 | } | ||
| 68 | |||
| 69 | writer.WriteEndElement(); | ||
| 70 | } | ||
| 71 | |||
| 72 | private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchSymbol searchSymbol) | ||
| 73 | { | ||
| 74 | writer.WriteStartElement((0 == (searchSymbol.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); | ||
| 75 | |||
| 76 | base.WriteXml(writer); | ||
| 77 | |||
| 78 | writer.WriteAttributeString("Path", searchSymbol.Path); | ||
| 79 | if (WixFileSearchAttributes.WantExists == (searchSymbol.Attributes & WixFileSearchAttributes.WantExists)) | ||
| 80 | { | ||
| 81 | writer.WriteAttributeString("Type", "exists"); | ||
| 82 | } | ||
| 83 | else if (WixFileSearchAttributes.WantVersion == (searchSymbol.Attributes & WixFileSearchAttributes.WantVersion)) | ||
| 84 | { | ||
| 85 | // Can never get here for DirectorySearch. | ||
| 86 | writer.WriteAttributeString("Type", "version"); | ||
| 87 | } | ||
| 88 | else | ||
| 89 | { | ||
| 90 | writer.WriteAttributeString("Type", "path"); | ||
| 91 | } | ||
| 92 | writer.WriteEndElement(); | ||
| 93 | } | ||
| 94 | |||
| 95 | private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchSymbol symbol) | ||
| 96 | { | ||
| 97 | writer.WriteStartElement("MsiProductSearch"); | ||
| 98 | |||
| 99 | base.WriteXml(writer); | ||
| 100 | |||
| 101 | if (0 != (symbol.Attributes & WixProductSearchAttributes.UpgradeCode)) | ||
| 102 | { | ||
| 103 | writer.WriteAttributeString("UpgradeCode", symbol.Guid); | ||
| 104 | } | ||
| 105 | else | ||
| 106 | { | ||
| 107 | writer.WriteAttributeString("ProductCode", symbol.Guid); | ||
| 108 | } | ||
| 109 | |||
| 110 | if (0 != (symbol.Attributes & WixProductSearchAttributes.Version)) | ||
| 111 | { | ||
| 112 | writer.WriteAttributeString("Type", "version"); | ||
| 113 | } | ||
| 114 | else if (0 != (symbol.Attributes & WixProductSearchAttributes.Language)) | ||
| 115 | { | ||
| 116 | writer.WriteAttributeString("Type", "language"); | ||
| 117 | } | ||
| 118 | else if (0 != (symbol.Attributes & WixProductSearchAttributes.State)) | ||
| 119 | { | ||
| 120 | writer.WriteAttributeString("Type", "state"); | ||
| 121 | } | ||
| 122 | else if (0 != (symbol.Attributes & WixProductSearchAttributes.Assignment)) | ||
| 123 | { | ||
| 124 | writer.WriteAttributeString("Type", "assignment"); | ||
| 125 | } | ||
| 126 | |||
| 127 | writer.WriteEndElement(); | ||
| 128 | } | ||
| 129 | |||
| 130 | private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchSymbol symbol) | ||
| 131 | { | ||
| 132 | writer.WriteStartElement("RegistrySearch"); | ||
| 133 | |||
| 134 | base.WriteXml(writer); | ||
| 135 | |||
| 136 | switch (symbol.Root) | ||
| 137 | { | ||
| 138 | case RegistryRootType.ClassesRoot: | ||
| 139 | writer.WriteAttributeString("Root", "HKCR"); | ||
| 140 | break; | ||
| 141 | case RegistryRootType.CurrentUser: | ||
| 142 | writer.WriteAttributeString("Root", "HKCU"); | ||
| 143 | break; | ||
| 144 | case RegistryRootType.LocalMachine: | ||
| 145 | writer.WriteAttributeString("Root", "HKLM"); | ||
| 146 | break; | ||
| 147 | case RegistryRootType.Users: | ||
| 148 | writer.WriteAttributeString("Root", "HKU"); | ||
| 149 | break; | ||
| 150 | } | ||
| 151 | |||
| 152 | writer.WriteAttributeString("Key", symbol.Key); | ||
| 153 | |||
| 154 | if (!String.IsNullOrEmpty(symbol.Value)) | ||
| 155 | { | ||
| 156 | writer.WriteAttributeString("Value", symbol.Value); | ||
| 157 | } | ||
| 158 | |||
| 159 | var existenceOnly = 0 != (symbol.Attributes & WixRegistrySearchAttributes.WantExists); | ||
| 160 | |||
| 161 | writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); | ||
| 162 | |||
| 163 | if (0 != (symbol.Attributes & WixRegistrySearchAttributes.Win64)) | ||
| 164 | { | ||
| 165 | writer.WriteAttributeString("Win64", "yes"); | ||
| 166 | } | ||
| 167 | |||
| 168 | if (!existenceOnly) | ||
| 169 | { | ||
| 170 | if (0 != (symbol.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) | ||
| 171 | { | ||
| 172 | writer.WriteAttributeString("ExpandEnvironment", "yes"); | ||
| 173 | } | ||
| 174 | |||
| 175 | // We *always* say this is VariableType="string". If we end up | ||
| 176 | // needing to be more specific, we will have to expand the "Format" | ||
| 177 | // attribute to allow "number" and "version". | ||
| 178 | |||
| 179 | writer.WriteAttributeString("VariableType", "string"); | ||
| 180 | } | ||
| 181 | |||
| 182 | writer.WriteEndElement(); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs new file mode 100644 index 00000000..f9ff23cb --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs | |||
| @@ -0,0 +1,133 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Text; | ||
| 10 | using System.Xml; | ||
| 11 | using WixToolset.Core.Native.Msi; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | |||
| 15 | internal class ProcessBundleSoftwareTagsCommand | ||
| 16 | { | ||
| 17 | public ProcessBundleSoftwareTagsCommand(IntermediateSection section, IEnumerable<WixBundleTagSymbol> softwareTags) | ||
| 18 | { | ||
| 19 | this.Section = section; | ||
| 20 | this.SoftwareTags = softwareTags; | ||
| 21 | } | ||
| 22 | |||
| 23 | private IntermediateSection Section { get; } | ||
| 24 | |||
| 25 | private IEnumerable<WixBundleTagSymbol> SoftwareTags { get; } | ||
| 26 | |||
| 27 | public void Execute() | ||
| 28 | { | ||
| 29 | var bundleInfo = this.Section.Symbols.OfType<WixBundleSymbol>().FirstOrDefault(); | ||
| 30 | var bundleId = NormalizeGuid(bundleInfo.BundleId); | ||
| 31 | var upgradeCode = NormalizeGuid(bundleInfo.UpgradeCode); | ||
| 32 | |||
| 33 | var uniqueId = String.Concat("wix:bundle/", bundleId); | ||
| 34 | var persistentId = String.Concat("wix:bundle.upgrade/", upgradeCode); | ||
| 35 | |||
| 36 | // Try to collect all the software id tags from all the child packages. | ||
| 37 | var containedTags = CollectPackageTags(this.Section); | ||
| 38 | |||
| 39 | foreach (var bundleTag in this.SoftwareTags) | ||
| 40 | { | ||
| 41 | using (var ms = new MemoryStream()) | ||
| 42 | { | ||
| 43 | CreateTagFile(ms, uniqueId, bundleInfo.Name, bundleInfo.Version, bundleTag.Regid, bundleInfo.Manufacturer, persistentId, containedTags); | ||
| 44 | bundleTag.Xml = Encoding.UTF8.GetString(ms.ToArray()); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | private static string NormalizeGuid(string guidString) | ||
| 50 | { | ||
| 51 | if (Guid.TryParse(guidString, out var guid)) | ||
| 52 | { | ||
| 53 | return guid.ToString("D").ToUpperInvariant(); | ||
| 54 | } | ||
| 55 | |||
| 56 | return guidString; | ||
| 57 | } | ||
| 58 | |||
| 59 | private static IEnumerable<SoftwareTag> CollectPackageTags(IntermediateSection section) | ||
| 60 | { | ||
| 61 | var tags = new List<SoftwareTag>(); | ||
| 62 | |||
| 63 | var msiPackages = section.Symbols.OfType<WixBundlePackageSymbol>().Where(s => s.Type == WixBundlePackageType.Msi).ToList(); | ||
| 64 | if (msiPackages.Any()) | ||
| 65 | { | ||
| 66 | var payloadSymbolsById = section.Symbols.OfType<WixBundlePayloadSymbol>().ToDictionary(s => s.Id.Id); | ||
| 67 | |||
| 68 | foreach (var msiPackage in msiPackages) | ||
| 69 | { | ||
| 70 | var payload = payloadSymbolsById[msiPackage.PayloadRef]; | ||
| 71 | |||
| 72 | using (var db = new Database(payload.SourceFile.Path, OpenDatabase.ReadOnly)) | ||
| 73 | { | ||
| 74 | using (var view = db.OpenExecuteView("SELECT `Regid`, `TagId` FROM `SoftwareIdentificationTag`")) | ||
| 75 | { | ||
| 76 | foreach (var record in view.Records) | ||
| 77 | { | ||
| 78 | tags.Add(new SoftwareTag { Regid = record.GetString(1), Id = record.GetString(2) }); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | return tags; | ||
| 86 | } | ||
| 87 | |||
| 88 | private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId, IEnumerable<SoftwareTag> containedTags) | ||
| 89 | { | ||
| 90 | var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; | ||
| 91 | |||
| 92 | using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) | ||
| 93 | { | ||
| 94 | writer.WriteStartDocument(); | ||
| 95 | writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); | ||
| 96 | writer.WriteAttributeString("tagId", uniqueId); | ||
| 97 | writer.WriteAttributeString("name", name); | ||
| 98 | writer.WriteAttributeString("version", version); | ||
| 99 | writer.WriteAttributeString("versionScheme", versionScheme); | ||
| 100 | |||
| 101 | writer.WriteStartElement("Entity"); | ||
| 102 | writer.WriteAttributeString("name", manufacturer); | ||
| 103 | writer.WriteAttributeString("regid", regid); | ||
| 104 | writer.WriteAttributeString("role", "softwareCreator tagCreator"); | ||
| 105 | writer.WriteEndElement(); // </Entity> | ||
| 106 | |||
| 107 | if (!String.IsNullOrEmpty(persistendId)) | ||
| 108 | { | ||
| 109 | writer.WriteStartElement("Meta"); | ||
| 110 | writer.WriteAttributeString("persistentId", persistendId); | ||
| 111 | writer.WriteEndElement(); // </Meta> | ||
| 112 | } | ||
| 113 | |||
| 114 | foreach (var containedTag in containedTags) | ||
| 115 | { | ||
| 116 | writer.WriteStartElement("Link"); | ||
| 117 | writer.WriteAttributeString("rel", "component"); | ||
| 118 | writer.WriteAttributeString("href", String.Concat("swid:", containedTag.Id)); | ||
| 119 | writer.WriteEndElement(); // </Link> | ||
| 120 | } | ||
| 121 | |||
| 122 | writer.WriteEndElement(); // </SoftwareIdentity> | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | private class SoftwareTag | ||
| 127 | { | ||
| 128 | public string Regid { get; set; } | ||
| 129 | |||
| 130 | public string Id { get; set; } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs new file mode 100644 index 00000000..99effbc7 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs | |||
| @@ -0,0 +1,147 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Core.Burn.Bundles; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | |||
| 13 | internal class ProcessDependencyProvidersCommand | ||
| 14 | { | ||
| 15 | public ProcessDependencyProvidersCommand(IMessaging messaging, IntermediateSection section, IDictionary<string, PackageFacade> facades) | ||
| 16 | { | ||
| 17 | this.Messaging = messaging; | ||
| 18 | this.Section = section; | ||
| 19 | |||
| 20 | this.Facades = facades; | ||
| 21 | } | ||
| 22 | |||
| 23 | public string BundleProviderKey { get; private set; } | ||
| 24 | |||
| 25 | public Dictionary<string, WixDependencyProviderSymbol> DependencySymbolsByKey { get; private set; } | ||
| 26 | |||
| 27 | private IMessaging Messaging { get; } | ||
| 28 | |||
| 29 | private IntermediateSection Section { get; } | ||
| 30 | |||
| 31 | private IDictionary<string, PackageFacade> Facades { get; } | ||
| 32 | |||
| 33 | /// <summary> | ||
| 34 | /// Sets the explicitly provided bundle provider key, if provided. And... | ||
| 35 | /// Imports authored dependency providers for each package in the manifest, | ||
| 36 | /// and generates dependency providers for certain package types that do not | ||
| 37 | /// have a provider defined. | ||
| 38 | /// </summary> | ||
| 39 | public void Execute() | ||
| 40 | { | ||
| 41 | var dependencySymbols = this.Section.Symbols.OfType<WixDependencyProviderSymbol>(); | ||
| 42 | |||
| 43 | foreach (var dependency in dependencySymbols) | ||
| 44 | { | ||
| 45 | // Sets the provider key for the bundle, if it is not set already. | ||
| 46 | if (String.IsNullOrEmpty(this.BundleProviderKey)) | ||
| 47 | { | ||
| 48 | if (dependency.Bundle) | ||
| 49 | { | ||
| 50 | this.BundleProviderKey = dependency.ProviderKey; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | // Import any authored dependencies. These may merge with imported provides from MSI packages. | ||
| 55 | var packageId = dependency.ParentRef; | ||
| 56 | |||
| 57 | if (this.Facades.TryGetValue(packageId, out var facade)) | ||
| 58 | { | ||
| 59 | if (String.IsNullOrEmpty(dependency.ProviderKey)) | ||
| 60 | { | ||
| 61 | switch (facade.SpecificPackageSymbol) | ||
| 62 | { | ||
| 63 | // The WixDependencyExtension allows an empty Key for MSIs and MSPs. | ||
| 64 | case WixBundleMsiPackageSymbol msiPackage: | ||
| 65 | dependency.ProviderKey = msiPackage.ProductCode; | ||
| 66 | break; | ||
| 67 | case WixBundleMspPackageSymbol mspPackage: | ||
| 68 | dependency.ProviderKey = mspPackage.PatchCode; | ||
| 69 | break; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | if (String.IsNullOrEmpty(dependency.Version)) | ||
| 74 | { | ||
| 75 | dependency.Version = facade.PackageSymbol.Version; | ||
| 76 | } | ||
| 77 | |||
| 78 | // If the version is still missing, a version could not be gathered from the package and was not authored. | ||
| 79 | if (String.IsNullOrEmpty(dependency.Version)) | ||
| 80 | { | ||
| 81 | this.Messaging.Write(ErrorMessages.MissingDependencyVersion(facade.PackageId)); | ||
| 82 | } | ||
| 83 | |||
| 84 | if (String.IsNullOrEmpty(dependency.DisplayName)) | ||
| 85 | { | ||
| 86 | dependency.DisplayName = facade.PackageSymbol.DisplayName; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | this.DependencySymbolsByKey = this.GetDependencySymbolsByKey(dependencySymbols); | ||
| 92 | |||
| 93 | // Generate providers for MSI and MSP packages that still do not have providers. | ||
| 94 | foreach (var facade in this.Facades.Values) | ||
| 95 | { | ||
| 96 | string key = null; | ||
| 97 | |||
| 98 | if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) | ||
| 99 | { | ||
| 100 | key = msiPackage.ProductCode; | ||
| 101 | } | ||
| 102 | else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) | ||
| 103 | { | ||
| 104 | key = mspPackage.PatchCode; | ||
| 105 | } | ||
| 106 | |||
| 107 | if (!String.IsNullOrEmpty(key) && !this.DependencySymbolsByKey.ContainsKey(key)) | ||
| 108 | { | ||
| 109 | var dependency = this.Section.AddSymbol(new WixDependencyProviderSymbol(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.Id) | ||
| 110 | { | ||
| 111 | ParentRef = facade.PackageId, | ||
| 112 | ProviderKey = key, | ||
| 113 | Version = facade.PackageSymbol.Version, | ||
| 114 | DisplayName = facade.PackageSymbol.DisplayName | ||
| 115 | }); | ||
| 116 | |||
| 117 | this.DependencySymbolsByKey.Add(dependency.ProviderKey, dependency); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | private Dictionary<string, WixDependencyProviderSymbol> GetDependencySymbolsByKey(IEnumerable<WixDependencyProviderSymbol> dependencySymbols) | ||
| 123 | { | ||
| 124 | var dependencySymbolsByKey = new Dictionary<string, WixDependencyProviderSymbol>(); | ||
| 125 | |||
| 126 | foreach (var dependency in dependencySymbols) | ||
| 127 | { | ||
| 128 | if (dependencySymbolsByKey.TryGetValue(dependency.ProviderKey, out var collision)) | ||
| 129 | { | ||
| 130 | // If not a perfect dependency collision, display an error. | ||
| 131 | if (dependency.ProviderKey != collision.ProviderKey || | ||
| 132 | dependency.Version != collision.Version || | ||
| 133 | dependency.DisplayName != collision.DisplayName) | ||
| 134 | { | ||
| 135 | this.Messaging.Write(ErrorMessages.DuplicateProviderDependencyKey(dependency.ProviderKey, dependency.ParentRef)); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | else | ||
| 139 | { | ||
| 140 | dependencySymbolsByKey.Add(dependency.ProviderKey, dependency); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | return dependencySymbolsByKey; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs new file mode 100644 index 00000000..c678b114 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs | |||
| @@ -0,0 +1,128 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Burn; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class ResolveDownloadUrlsCommand | ||
| 14 | { | ||
| 15 | public ResolveDownloadUrlsCommand(IMessaging messaging, IEnumerable<IBurnBackendBinderExtension> backendExtensions, IEnumerable<WixBundleContainerSymbol> containers, Dictionary<string, WixBundlePayloadSymbol> payloadsById) | ||
| 16 | { | ||
| 17 | this.Messaging = messaging; | ||
| 18 | this.BackendExtensions = backendExtensions; | ||
| 19 | this.Containers = containers; | ||
| 20 | this.PayloadsById = payloadsById; | ||
| 21 | } | ||
| 22 | |||
| 23 | private IMessaging Messaging { get; } | ||
| 24 | |||
| 25 | private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; } | ||
| 26 | |||
| 27 | private IEnumerable<WixBundleContainerSymbol> Containers { get; } | ||
| 28 | |||
| 29 | private Dictionary<string, WixBundlePayloadSymbol> PayloadsById { get; } | ||
| 30 | |||
| 31 | public void Execute() | ||
| 32 | { | ||
| 33 | this.ResolveContainerUrls(); | ||
| 34 | |||
| 35 | this.ResolvePayloadUrls(); | ||
| 36 | } | ||
| 37 | |||
| 38 | private void ResolveContainerUrls() | ||
| 39 | { | ||
| 40 | foreach (var container in this.Containers) | ||
| 41 | { | ||
| 42 | if (container.Type == ContainerType.Detached) | ||
| 43 | { | ||
| 44 | var resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id.Id, container.Name); | ||
| 45 | if (!String.IsNullOrEmpty(resolvedUrl)) | ||
| 46 | { | ||
| 47 | container.DownloadUrl = resolvedUrl; | ||
| 48 | } | ||
| 49 | } | ||
| 50 | else if (container.Type == ContainerType.Attached) | ||
| 51 | { | ||
| 52 | if (!String.IsNullOrEmpty(container.DownloadUrl)) | ||
| 53 | { | ||
| 54 | this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id.Id)); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | private void ResolvePayloadUrls() | ||
| 61 | { | ||
| 62 | foreach (var payload in this.PayloadsById.Values) | ||
| 63 | { | ||
| 64 | if (payload.Packaging == PackagingType.Embedded && payload.ContainerRef == BurnConstants.BurnUXContainerName) | ||
| 65 | { | ||
| 66 | if (!String.IsNullOrEmpty(payload.DownloadUrl)) | ||
| 67 | { | ||
| 68 | this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(payload.SourceLineNumbers, payload.Id.Id)); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | else | ||
| 72 | { | ||
| 73 | var packageId = payload.ParentPackagePayloadRef; | ||
| 74 | var parentUrl = payload.ParentPackagePayloadRef == null ? null : this.PayloadsById[payload.ParentPackagePayloadRef].DownloadUrl; | ||
| 75 | var resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id.Id, payload.Name); | ||
| 76 | if (!String.IsNullOrEmpty(resolvedUrl)) | ||
| 77 | { | ||
| 78 | payload.DownloadUrl = resolvedUrl; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) | ||
| 85 | { | ||
| 86 | string resolvedUrl = null; | ||
| 87 | |||
| 88 | foreach (var extension in this.BackendExtensions) | ||
| 89 | { | ||
| 90 | resolvedUrl = extension.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName); | ||
| 91 | if (!String.IsNullOrEmpty(resolvedUrl)) | ||
| 92 | { | ||
| 93 | break; | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | if (String.IsNullOrEmpty(resolvedUrl)) | ||
| 98 | { | ||
| 99 | // If a URL was not specified but there is a fallback URL that has a format specifier in it | ||
| 100 | // then use the fallback URL formatter for this URL. | ||
| 101 | if (String.IsNullOrEmpty(url) && !String.IsNullOrEmpty(fallbackUrl)) | ||
| 102 | { | ||
| 103 | var formattedFallbackUrl = String.Format(fallbackUrl, packageId, payloadId, fileName); | ||
| 104 | if (!String.Equals(fallbackUrl, formattedFallbackUrl, StringComparison.OrdinalIgnoreCase)) | ||
| 105 | { | ||
| 106 | url = fallbackUrl; | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | if (!String.IsNullOrEmpty(url)) | ||
| 111 | { | ||
| 112 | var formattedUrl = String.Format(url, packageId, payloadId, fileName); | ||
| 113 | |||
| 114 | if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out var canonicalUri)) | ||
| 115 | { | ||
| 116 | resolvedUrl = canonicalUri.AbsoluteUri; | ||
| 117 | } | ||
| 118 | else | ||
| 119 | { | ||
| 120 | resolvedUrl = null; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | return resolvedUrl; | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs new file mode 100644 index 00000000..e88f26ef --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System.Xml; | ||
| 6 | using WixToolset.Data.Symbols; | ||
| 7 | |||
| 8 | internal class SetVariableSearchFacade : BaseSearchFacade | ||
| 9 | { | ||
| 10 | public SetVariableSearchFacade(WixSearchSymbol searchSymbol, WixSetVariableSymbol setVariableSymbol) | ||
| 11 | { | ||
| 12 | this.SearchSymbol = searchSymbol; | ||
| 13 | this.SetVariableSymbol = setVariableSymbol; | ||
| 14 | } | ||
| 15 | |||
| 16 | private WixSetVariableSymbol SetVariableSymbol { get; } | ||
| 17 | |||
| 18 | public override void WriteXml(XmlTextWriter writer) | ||
| 19 | { | ||
| 20 | writer.WriteStartElement("SetVariable"); | ||
| 21 | |||
| 22 | base.WriteXml(writer); | ||
| 23 | |||
| 24 | if (this.SetVariableSymbol.Type != WixBundleVariableType.Unknown) | ||
| 25 | { | ||
| 26 | writer.WriteAttributeString("Value", this.SetVariableSymbol.Value); | ||
| 27 | |||
| 28 | switch (this.SetVariableSymbol.Type) | ||
| 29 | { | ||
| 30 | case WixBundleVariableType.Formatted: | ||
| 31 | writer.WriteAttributeString("Type", "formatted"); | ||
| 32 | break; | ||
| 33 | case WixBundleVariableType.Numeric: | ||
| 34 | writer.WriteAttributeString("Type", "numeric"); | ||
| 35 | break; | ||
| 36 | case WixBundleVariableType.String: | ||
| 37 | writer.WriteAttributeString("Type", "string"); | ||
| 38 | break; | ||
| 39 | case WixBundleVariableType.Version: | ||
| 40 | writer.WriteAttributeString("Type", "version"); | ||
| 41 | break; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | writer.WriteEndElement(); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/BundleBackend.cs b/src/wix/WixToolset.Core.Burn/BundleBackend.cs new file mode 100644 index 00000000..60e9ea60 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BundleBackend.cs | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset.Core.Burn.Bundles; | ||
| 8 | using WixToolset.Core.Burn.Inscribe; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class BundleBackend : IBackend | ||
| 15 | { | ||
| 16 | public IBindResult Bind(IBindContext context) | ||
| 17 | { | ||
| 18 | var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); | ||
| 19 | |||
| 20 | var backendExtensions = extensionManager.GetServices<IBurnBackendBinderExtension>(); | ||
| 21 | |||
| 22 | foreach (var extension in backendExtensions) | ||
| 23 | { | ||
| 24 | extension.PreBackendBind(context); | ||
| 25 | } | ||
| 26 | |||
| 27 | var command = new BindBundleCommand(context, backendExtensions); | ||
| 28 | command.Execute(); | ||
| 29 | |||
| 30 | var result = context.ServiceProvider.GetService<IBindResult>(); | ||
| 31 | result.FileTransfers = command.FileTransfers; | ||
| 32 | result.TrackedFiles = command.TrackedFiles; | ||
| 33 | result.Wixout = command.Wixout; | ||
| 34 | |||
| 35 | foreach (var extension in backendExtensions) | ||
| 36 | { | ||
| 37 | extension.PostBackendBind(result); | ||
| 38 | } | ||
| 39 | |||
| 40 | return result; | ||
| 41 | } | ||
| 42 | |||
| 43 | public IDecompileResult Decompile(IDecompileContext context) | ||
| 44 | { | ||
| 45 | throw new NotImplementedException(); | ||
| 46 | } | ||
| 47 | |||
| 48 | public bool Inscribe(IInscribeContext context) | ||
| 49 | { | ||
| 50 | if (String.IsNullOrEmpty(context.SignedEngineFile)) | ||
| 51 | { | ||
| 52 | var command = new InscribeBundleCommand(context); | ||
| 53 | return command.Execute(); | ||
| 54 | } | ||
| 55 | else | ||
| 56 | { | ||
| 57 | var command = new InscribeBundleEngineCommand(context); | ||
| 58 | return command.Execute(); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | public Intermediate Unbind(IUnbindContext context) | ||
| 63 | { | ||
| 64 | var uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); | ||
| 65 | var acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); | ||
| 66 | var messaging = context.ServiceProvider.GetService<IMessaging>(); | ||
| 67 | |||
| 68 | using (var reader = BurnReader.Open(messaging, context.InputFilePath)) | ||
| 69 | { | ||
| 70 | reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder); | ||
| 71 | reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder); | ||
| 72 | } | ||
| 73 | |||
| 74 | return null; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs new file mode 100644 index 00000000..75c60e56 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs | |||
| @@ -0,0 +1,117 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | |||
| 12 | internal class AutomaticallySlipstreamPatchesCommand | ||
| 13 | { | ||
| 14 | public AutomaticallySlipstreamPatchesCommand(IntermediateSection section, ICollection<PackageFacade> packageFacades) | ||
| 15 | { | ||
| 16 | this.Section = section; | ||
| 17 | this.PackageFacades = packageFacades; | ||
| 18 | } | ||
| 19 | |||
| 20 | private IntermediateSection Section { get; } | ||
| 21 | |||
| 22 | private IEnumerable<PackageFacade> PackageFacades { get; } | ||
| 23 | |||
| 24 | public void Execute() | ||
| 25 | { | ||
| 26 | var msiPackages = new List<WixBundleMsiPackageSymbol>(); | ||
| 27 | var targetsProductCode = new Dictionary<string, List<WixBundlePatchTargetCodeSymbol>>(); | ||
| 28 | var targetsUpgradeCode = new Dictionary<string, List<WixBundlePatchTargetCodeSymbol>>(); | ||
| 29 | |||
| 30 | foreach (var facade in this.PackageFacades) | ||
| 31 | { | ||
| 32 | // Keep track of all MSI packages. | ||
| 33 | if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) | ||
| 34 | { | ||
| 35 | msiPackages.Add(msiPackage); | ||
| 36 | } | ||
| 37 | else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage && mspPackage.Slipstream) | ||
| 38 | { | ||
| 39 | var patchTargetCodeSymbols = this.Section.Symbols | ||
| 40 | .OfType<WixBundlePatchTargetCodeSymbol>() | ||
| 41 | .Where(r => r.PackageRef == facade.PackageId); | ||
| 42 | |||
| 43 | // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. | ||
| 44 | foreach (var symbol in patchTargetCodeSymbols) | ||
| 45 | { | ||
| 46 | if (symbol.TargetsProductCode) | ||
| 47 | { | ||
| 48 | if (!targetsProductCode.TryGetValue(symbol.TargetCode, out var symbols)) | ||
| 49 | { | ||
| 50 | symbols = new List<WixBundlePatchTargetCodeSymbol>(); | ||
| 51 | targetsProductCode.Add(symbol.TargetCode, symbols); | ||
| 52 | } | ||
| 53 | |||
| 54 | symbols.Add(symbol); | ||
| 55 | } | ||
| 56 | else if (symbol.TargetsUpgradeCode) | ||
| 57 | { | ||
| 58 | if (!targetsUpgradeCode.TryGetValue(symbol.TargetCode, out var symbols)) | ||
| 59 | { | ||
| 60 | symbols = new List<WixBundlePatchTargetCodeSymbol>(); | ||
| 61 | targetsUpgradeCode.Add(symbol.TargetCode, symbols); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | var slipstreamMspIds = new HashSet<string>(); | ||
| 69 | |||
| 70 | // Loop through the MSI and slipstream patches targeting it. | ||
| 71 | foreach (var msi in msiPackages) | ||
| 72 | { | ||
| 73 | if (targetsProductCode.TryGetValue(msi.ProductCode, out var symbols)) | ||
| 74 | { | ||
| 75 | foreach (var symbol in symbols) | ||
| 76 | { | ||
| 77 | Debug.Assert(symbol.TargetsProductCode); | ||
| 78 | Debug.Assert(!symbol.TargetsUpgradeCode); | ||
| 79 | |||
| 80 | this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out symbols)) | ||
| 85 | { | ||
| 86 | foreach (var symbol in symbols) | ||
| 87 | { | ||
| 88 | Debug.Assert(!symbol.TargetsProductCode); | ||
| 89 | Debug.Assert(symbol.TargetsUpgradeCode); | ||
| 90 | |||
| 91 | this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); | ||
| 92 | } | ||
| 93 | |||
| 94 | symbols = null; | ||
| 95 | } | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | private bool TryAddSlipstreamSymbol(HashSet<string> slipstreamMspIds, WixBundleMsiPackageSymbol msiPackage, WixBundlePatchTargetCodeSymbol patchTargetCode) | ||
| 100 | { | ||
| 101 | var id = new Identifier(AccessModifier.Section, msiPackage.Id.Id, patchTargetCode.PackageRef); | ||
| 102 | |||
| 103 | if (slipstreamMspIds.Add(id.Id)) | ||
| 104 | { | ||
| 105 | this.Section.AddSymbol(new WixBundleSlipstreamMspSymbol(patchTargetCode.SourceLineNumbers) | ||
| 106 | { | ||
| 107 | TargetPackageRef = msiPackage.Id.Id, | ||
| 108 | MspPackageRef = patchTargetCode.PackageRef, | ||
| 109 | }); | ||
| 110 | |||
| 111 | return true; | ||
| 112 | } | ||
| 113 | |||
| 114 | return false; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs b/src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs new file mode 100644 index 00000000..3b4a4156 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Security.Cryptography; | ||
| 7 | using System.Text; | ||
| 8 | |||
| 9 | internal static class BundleHashAlgorithm | ||
| 10 | { | ||
| 11 | public static string Hash(FileInfo fileInfo) | ||
| 12 | { | ||
| 13 | byte[] hashBytes; | ||
| 14 | |||
| 15 | using (var managed = new SHA512CryptoServiceProvider()) | ||
| 16 | using (var stream = fileInfo.OpenRead()) | ||
| 17 | { | ||
| 18 | hashBytes = managed.ComputeHash(stream); | ||
| 19 | } | ||
| 20 | |||
| 21 | var sb = new StringBuilder(hashBytes.Length * 2); | ||
| 22 | for (var i = 0; i < hashBytes.Length; i++) | ||
| 23 | { | ||
| 24 | sb.AppendFormat("{0:X2}", hashBytes[i]); | ||
| 25 | } | ||
| 26 | |||
| 27 | return sb.ToString(); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs new file mode 100644 index 00000000..1eb3563a --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs | |||
| @@ -0,0 +1,385 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | /// <summary> | ||
| 12 | /// Common functionality for Burn PE Writer & Reader for the WiX toolset. | ||
| 13 | /// </summary> | ||
| 14 | /// <remarks>This class encapsulates common functionality related to | ||
| 15 | /// bundled/chained setup packages.</remarks> | ||
| 16 | /// <example> | ||
| 17 | /// </example> | ||
| 18 | internal abstract class BurnCommon : IDisposable | ||
| 19 | { | ||
| 20 | public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; | ||
| 21 | public const string BurnUXContainerEmbeddedIdFormat = "u{0}"; | ||
| 22 | public const string BurnAuthoredContainerEmbeddedIdFormat = "a{0}"; | ||
| 23 | |||
| 24 | public const string BADataFileName = "BootstrapperApplicationData.xml"; | ||
| 25 | public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; | ||
| 26 | |||
| 27 | public const string BundleExtensionDataFileName = "BundleExtensionData.xml"; | ||
| 28 | public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; | ||
| 29 | |||
| 30 | // See WinNT.h for details about the PE format, including the | ||
| 31 | // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, | ||
| 32 | // IMAGE_FILE_HEADER, etc. | ||
| 33 | protected const UInt32 IMAGE_DOS_HEADER_SIZE = 64; | ||
| 34 | protected const UInt32 IMAGE_DOS_HEADER_OFFSET_MAGIC = 0; | ||
| 35 | protected const UInt32 IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60; | ||
| 36 | |||
| 37 | protected const UInt32 IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20) | ||
| 38 | protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0; | ||
| 39 | protected const UInt32 IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6; | ||
| 40 | protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20; | ||
| 41 | |||
| 42 | protected const UInt32 IMAGE_OPTIONAL_OFFSET_CHECKSUM = 4 * 16; // checksum is 16 DWORDs into IMAGE_OPTIONAL_HEADER which is right after the IMAGE_NT_HEADER. | ||
| 43 | protected const UInt32 IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY)); | ||
| 44 | |||
| 45 | protected const UInt32 IMAGE_SECTION_HEADER_SIZE = 40; | ||
| 46 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_NAME = 0; | ||
| 47 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8; | ||
| 48 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16; | ||
| 49 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20; | ||
| 50 | |||
| 51 | protected const UInt32 IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs. | ||
| 52 | protected const UInt32 IMAGE_DIRECTORY_ENTRY_SECURITY = 4; | ||
| 53 | protected const UInt32 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; | ||
| 54 | |||
| 55 | protected const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D; | ||
| 56 | protected const UInt32 IMAGE_NT_SIGNATURE = 0x00004550; | ||
| 57 | protected const UInt64 IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword. | ||
| 58 | |||
| 59 | // The ".wixburn" section contains: | ||
| 60 | // 0- 3: magic number | ||
| 61 | // 4- 7: version | ||
| 62 | // 8-23: bundle GUID | ||
| 63 | // 24-27: engine (stub) size | ||
| 64 | // 28-31: original checksum | ||
| 65 | // 32-35: original signature offset | ||
| 66 | // 36-39: original signature size | ||
| 67 | // 40-43: container type (1 = CAB) | ||
| 68 | // 44-47: container count | ||
| 69 | // 48-51: byte count of manifest + UX container | ||
| 70 | // 52-55: byte count of attached container | ||
| 71 | protected const UInt32 BURN_SECTION_OFFSET_MAGIC = 0; | ||
| 72 | protected const UInt32 BURN_SECTION_OFFSET_VERSION = 4; | ||
| 73 | protected const UInt32 BURN_SECTION_OFFSET_BUNDLEGUID = 8; | ||
| 74 | protected const UInt32 BURN_SECTION_OFFSET_STUBSIZE = 24; | ||
| 75 | protected const UInt32 BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28; | ||
| 76 | protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32; | ||
| 77 | protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36; | ||
| 78 | protected const UInt32 BURN_SECTION_OFFSET_FORMAT = 40; | ||
| 79 | protected const UInt32 BURN_SECTION_OFFSET_COUNT = 44; | ||
| 80 | protected const UInt32 BURN_SECTION_OFFSET_UXSIZE = 48; | ||
| 81 | protected const UInt32 BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE = 52; | ||
| 82 | protected const UInt32 BURN_SECTION_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE + 4; // last field + sizeof(DWORD) | ||
| 83 | |||
| 84 | protected const UInt32 BURN_SECTION_MAGIC = 0x00f14300; | ||
| 85 | protected const UInt32 BURN_SECTION_VERSION = 0x00000002; | ||
| 86 | protected string fileExe; | ||
| 87 | protected UInt32 peOffset = UInt32.MaxValue; | ||
| 88 | protected UInt16 sections = UInt16.MaxValue; | ||
| 89 | protected UInt32 firstSectionOffset = UInt32.MaxValue; | ||
| 90 | protected UInt32 checksumOffset; | ||
| 91 | protected UInt32 certificateTableSignatureOffset; | ||
| 92 | protected UInt32 certificateTableSignatureSize; | ||
| 93 | protected UInt32 wixburnDataOffset = UInt32.MaxValue; | ||
| 94 | |||
| 95 | // TODO: does this enum exist in another form somewhere? | ||
| 96 | /// <summary> | ||
| 97 | /// The types of attached containers that BurnWriter supports. | ||
| 98 | /// </summary> | ||
| 99 | public enum Container | ||
| 100 | { | ||
| 101 | Nothing = 0, | ||
| 102 | UX, | ||
| 103 | Attached | ||
| 104 | } | ||
| 105 | |||
| 106 | /// <summary> | ||
| 107 | /// Creates a BurnCommon for re-writing a PE file. | ||
| 108 | /// </summary> | ||
| 109 | /// <param name="messaging"></param> | ||
| 110 | /// <param name="fileExe">File to modify in-place.</param> | ||
| 111 | public BurnCommon(IMessaging messaging, string fileExe) | ||
| 112 | { | ||
| 113 | this.Messaging = messaging; | ||
| 114 | this.fileExe = fileExe; | ||
| 115 | } | ||
| 116 | |||
| 117 | public UInt32 Checksum { get; protected set; } | ||
| 118 | public UInt32 SignatureOffset { get; protected set; } | ||
| 119 | public UInt32 SignatureSize { get; protected set; } | ||
| 120 | public UInt32 Version { get; protected set; } | ||
| 121 | public UInt32 StubSize { get; protected set; } | ||
| 122 | public UInt32 OriginalChecksum { get; protected set; } | ||
| 123 | public UInt32 OriginalSignatureOffset { get; protected set; } | ||
| 124 | public UInt32 OriginalSignatureSize { get; protected set; } | ||
| 125 | public UInt32 EngineSize { get; protected set; } | ||
| 126 | public UInt32 ContainerCount { get; protected set; } | ||
| 127 | public UInt32 UXAddress { get; protected set; } | ||
| 128 | public UInt32 UXSize { get; protected set; } | ||
| 129 | public UInt32 AttachedContainerAddress { get; protected set; } | ||
| 130 | public UInt32 AttachedContainerSize { get; protected set; } | ||
| 131 | |||
| 132 | protected IMessaging Messaging { get; } | ||
| 133 | |||
| 134 | public void Dispose() | ||
| 135 | { | ||
| 136 | this.Dispose(true); | ||
| 137 | |||
| 138 | GC.SuppressFinalize(this); | ||
| 139 | } | ||
| 140 | |||
| 141 | /// <summary> | ||
| 142 | /// Copies one stream to another. | ||
| 143 | /// </summary> | ||
| 144 | /// <param name="input">Input stream.</param> | ||
| 145 | /// <param name="output">Output stream.</param> | ||
| 146 | /// <param name="size">Optional count of bytes to copy. 0 indicates whole input stream from current should be copied.</param> | ||
| 147 | protected static int CopyStream(Stream input, Stream output, int size) | ||
| 148 | { | ||
| 149 | var bytes = new byte[4096]; | ||
| 150 | var total = 0; | ||
| 151 | do | ||
| 152 | { | ||
| 153 | var read = Math.Min(bytes.Length, size - total); | ||
| 154 | read = input.Read(bytes, 0, read); | ||
| 155 | if (0 == read) | ||
| 156 | { | ||
| 157 | break; | ||
| 158 | } | ||
| 159 | |||
| 160 | output.Write(bytes, 0, read); | ||
| 161 | total += read; | ||
| 162 | } while (0 == size || total < size); | ||
| 163 | |||
| 164 | return total; | ||
| 165 | } | ||
| 166 | |||
| 167 | /// <summary> | ||
| 168 | /// Initialize the common information about a Burn engine. | ||
| 169 | /// </summary> | ||
| 170 | /// <param name="reader">Binary reader open against a Burn engine.</param> | ||
| 171 | /// <returns>True if initialized.</returns> | ||
| 172 | protected bool Initialize(BinaryReader reader) | ||
| 173 | { | ||
| 174 | if (!this.GetWixburnSectionInfo(reader)) | ||
| 175 | { | ||
| 176 | return false; | ||
| 177 | } | ||
| 178 | |||
| 179 | reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin); | ||
| 180 | byte[] bytes = reader.ReadBytes((int)BURN_SECTION_SIZE); | ||
| 181 | UInt32 uint32 = 0; | ||
| 182 | |||
| 183 | uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC); | ||
| 184 | if (BURN_SECTION_MAGIC != uint32) | ||
| 185 | { | ||
| 186 | this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe)); | ||
| 187 | return false; | ||
| 188 | } | ||
| 189 | |||
| 190 | this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION); | ||
| 191 | if (BURN_SECTION_VERSION != this.Version) | ||
| 192 | { | ||
| 193 | this.Messaging.Write(ErrorMessages.BundleTooNew(this.fileExe, this.Version)); | ||
| 194 | return false; | ||
| 195 | } | ||
| 196 | |||
| 197 | uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now | ||
| 198 | if (1 != uint32) | ||
| 199 | { | ||
| 200 | this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe)); | ||
| 201 | return false; | ||
| 202 | } | ||
| 203 | |||
| 204 | this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE); | ||
| 205 | this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM); | ||
| 206 | this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET); | ||
| 207 | this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE); | ||
| 208 | |||
| 209 | this.ContainerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT); | ||
| 210 | this.UXAddress = this.StubSize; | ||
| 211 | this.UXSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_UXSIZE); | ||
| 212 | |||
| 213 | // If there is an original signature use that to determine the engine size. | ||
| 214 | if (0 < this.OriginalSignatureOffset) | ||
| 215 | { | ||
| 216 | this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize; | ||
| 217 | } | ||
| 218 | else if (0 < this.SignatureOffset && 2 > this.ContainerCount) // if there is a signature and no attached containers, use the current signature. | ||
| 219 | { | ||
| 220 | this.EngineSize = this.SignatureOffset + this.SignatureSize; | ||
| 221 | } | ||
| 222 | else // just use the stub and UX container as the size of the engine. | ||
| 223 | { | ||
| 224 | this.EngineSize = this.StubSize + this.UXSize; | ||
| 225 | } | ||
| 226 | |||
| 227 | this.AttachedContainerAddress = this.ContainerCount > 1 ? this.EngineSize : 0; | ||
| 228 | this.AttachedContainerSize = this.ContainerCount > 1 ? BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE) : 0; | ||
| 229 | |||
| 230 | return true; | ||
| 231 | } | ||
| 232 | |||
| 233 | protected virtual void Dispose(bool disposing) | ||
| 234 | { | ||
| 235 | } | ||
| 236 | |||
| 237 | /// <summary> | ||
| 238 | /// Finds the ".wixburn" section in the current exe. | ||
| 239 | /// </summary> | ||
| 240 | /// <returns>true if the ".wixburn" section is successfully found; false otherwise</returns> | ||
| 241 | private bool GetWixburnSectionInfo(BinaryReader reader) | ||
| 242 | { | ||
| 243 | if (UInt32.MaxValue == this.wixburnDataOffset) | ||
| 244 | { | ||
| 245 | if (!this.EnsureNTHeader(reader)) | ||
| 246 | { | ||
| 247 | return false; | ||
| 248 | } | ||
| 249 | |||
| 250 | UInt32 wixburnSectionOffset = UInt32.MaxValue; | ||
| 251 | byte[] bytes = new byte[IMAGE_SECTION_HEADER_SIZE]; | ||
| 252 | |||
| 253 | reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin); | ||
| 254 | for (UInt16 sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex) | ||
| 255 | { | ||
| 256 | reader.Read(bytes, 0, bytes.Length); | ||
| 257 | |||
| 258 | if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME)) | ||
| 259 | { | ||
| 260 | wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex); | ||
| 261 | break; | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | if (UInt32.MaxValue == wixburnSectionOffset) | ||
| 266 | { | ||
| 267 | this.Messaging.Write(ErrorMessages.StubMissingWixburnSection(this.fileExe)); | ||
| 268 | return false; | ||
| 269 | } | ||
| 270 | |||
| 271 | // we need 56 bytes for the manifest header, which is always going to fit in | ||
| 272 | // the smallest alignment (512 bytes), but just to be paranoid... | ||
| 273 | if (BURN_SECTION_SIZE > BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA)) | ||
| 274 | { | ||
| 275 | this.Messaging.Write(ErrorMessages.StubWixburnSectionTooSmall(this.fileExe)); | ||
| 276 | return false; | ||
| 277 | } | ||
| 278 | |||
| 279 | this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA); | ||
| 280 | } | ||
| 281 | |||
| 282 | return true; | ||
| 283 | } | ||
| 284 | |||
| 285 | /// <summary> | ||
| 286 | /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe. | ||
| 287 | /// </summary> | ||
| 288 | /// <returns>true if the exe is a Windows executable; false otherwise</returns> | ||
| 289 | private bool EnsureNTHeader(BinaryReader reader) | ||
| 290 | { | ||
| 291 | if (UInt32.MaxValue == this.firstSectionOffset) | ||
| 292 | { | ||
| 293 | if (!this.EnsureDosHeader(reader)) | ||
| 294 | { | ||
| 295 | return false; | ||
| 296 | } | ||
| 297 | |||
| 298 | reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin); | ||
| 299 | byte[] bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE); | ||
| 300 | |||
| 301 | // Verify the NT signature... | ||
| 302 | if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE)) | ||
| 303 | { | ||
| 304 | this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe)); | ||
| 305 | return false; | ||
| 306 | } | ||
| 307 | |||
| 308 | ushort sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER); | ||
| 309 | |||
| 310 | this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS); | ||
| 311 | this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader; | ||
| 312 | |||
| 313 | this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM; | ||
| 314 | this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE; | ||
| 315 | this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset. | ||
| 316 | |||
| 317 | bytes = reader.ReadBytes(sizeOptionalHeader); | ||
| 318 | this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM); | ||
| 319 | this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE); | ||
| 320 | this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4); | ||
| 321 | } | ||
| 322 | |||
| 323 | return true; | ||
| 324 | } | ||
| 325 | |||
| 326 | /// <summary> | ||
| 327 | /// Checks for a valid DOS header in the current exe. | ||
| 328 | /// </summary> | ||
| 329 | /// <returns>true if the exe starts with a DOS stub; false otherwise</returns> | ||
| 330 | private bool EnsureDosHeader(BinaryReader reader) | ||
| 331 | { | ||
| 332 | if (UInt32.MaxValue == this.peOffset) | ||
| 333 | { | ||
| 334 | byte[] bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE); | ||
| 335 | |||
| 336 | // Verify the DOS 'MZ' signature. | ||
| 337 | if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC)) | ||
| 338 | { | ||
| 339 | this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe)); | ||
| 340 | return false; | ||
| 341 | } | ||
| 342 | |||
| 343 | this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER); | ||
| 344 | } | ||
| 345 | |||
| 346 | return true; | ||
| 347 | } | ||
| 348 | |||
| 349 | /// <summary> | ||
| 350 | /// Reads a UInt16 value in little-endian format from an offset in an array of bytes. | ||
| 351 | /// </summary> | ||
| 352 | /// <param name="bytes">Array from which to read.</param> | ||
| 353 | /// <param name="offset">Beginning offset from which to read.</param> | ||
| 354 | /// <returns>value at offset</returns> | ||
| 355 | internal static UInt16 ReadUInt16(byte[] bytes, UInt32 offset) | ||
| 356 | { | ||
| 357 | Debug.Assert(offset + 2 <= bytes.Length); | ||
| 358 | return (UInt16)(bytes[offset] + (bytes[offset + 1] << 8)); | ||
| 359 | } | ||
| 360 | |||
| 361 | /// <summary> | ||
| 362 | /// Reads a UInt32 value in little-endian format from an offset in an array of bytes. | ||
| 363 | /// </summary> | ||
| 364 | /// <param name="bytes">Array from which to read.</param> | ||
| 365 | /// <param name="offset">Beginning offset from which to read.</param> | ||
| 366 | /// <returns>value at offset</returns> | ||
| 367 | internal static UInt32 ReadUInt32(byte[] bytes, UInt32 offset) | ||
| 368 | { | ||
| 369 | Debug.Assert(offset + 4 <= bytes.Length); | ||
| 370 | return BurnCommon.ReadUInt16(bytes, offset) + ((UInt32)BurnCommon.ReadUInt16(bytes, offset + 2) << 16); | ||
| 371 | } | ||
| 372 | |||
| 373 | /// <summary> | ||
| 374 | /// Reads a UInt64 value in little-endian format from an offset in an array of bytes. | ||
| 375 | /// </summary> | ||
| 376 | /// <param name="bytes">Array from which to read.</param> | ||
| 377 | /// <param name="offset">Beginning offset from which to read.</param> | ||
| 378 | /// <returns>value at offset</returns> | ||
| 379 | internal static UInt64 ReadUInt64(byte[] bytes, UInt32 offset) | ||
| 380 | { | ||
| 381 | Debug.Assert(offset + 8 <= bytes.Length); | ||
| 382 | return BurnCommon.ReadUInt32(bytes, offset) + ((UInt64)BurnCommon.ReadUInt32(bytes, offset + 4) << 32); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs new file mode 100644 index 00000000..5b06b31e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs | |||
| @@ -0,0 +1,212 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.IO; | ||
| 9 | using System.Xml; | ||
| 10 | using WixToolset.Core.Native; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// Burn PE reader for the WiX toolset. | ||
| 15 | /// </summary> | ||
| 16 | /// <remarks>This class encapsulates reading from a stub EXE with containers attached | ||
| 17 | /// for dissecting bundled/chained setup packages.</remarks> | ||
| 18 | /// <example> | ||
| 19 | /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid)) | ||
| 20 | /// { | ||
| 21 | /// reader.ExtractUXContainer(file1, tempFolder); | ||
| 22 | /// } | ||
| 23 | /// </example> | ||
| 24 | internal class BurnReader : BurnCommon | ||
| 25 | { | ||
| 26 | private bool disposed; | ||
| 27 | |||
| 28 | private bool invalidBundle; | ||
| 29 | private BinaryReader binaryReader; | ||
| 30 | private readonly List<DictionaryEntry> attachedContainerPayloadNames; | ||
| 31 | |||
| 32 | /// <summary> | ||
| 33 | /// Creates a BurnReader for reading a PE file. | ||
| 34 | /// </summary> | ||
| 35 | /// <param name="messaging"></param> | ||
| 36 | /// <param name="fileExe">File to read.</param> | ||
| 37 | private BurnReader(IMessaging messaging, string fileExe) | ||
| 38 | : base(messaging, fileExe) | ||
| 39 | { | ||
| 40 | this.attachedContainerPayloadNames = new List<DictionaryEntry>(); | ||
| 41 | } | ||
| 42 | |||
| 43 | /// <summary> | ||
| 44 | /// Gets the underlying stream. | ||
| 45 | /// </summary> | ||
| 46 | public Stream Stream => this.binaryReader?.BaseStream; | ||
| 47 | |||
| 48 | internal static BurnReader Open(object inputFilePath) | ||
| 49 | { | ||
| 50 | throw new NotImplementedException(); | ||
| 51 | } | ||
| 52 | |||
| 53 | /// <summary> | ||
| 54 | /// Opens a Burn reader. | ||
| 55 | /// </summary> | ||
| 56 | /// <param name="messaging"></param> | ||
| 57 | /// <param name="fileExe">Path to file.</param> | ||
| 58 | /// <returns>Burn reader.</returns> | ||
| 59 | public static BurnReader Open(IMessaging messaging, string fileExe) | ||
| 60 | { | ||
| 61 | var reader = new BurnReader(messaging, fileExe); | ||
| 62 | |||
| 63 | reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)); | ||
| 64 | if (!reader.Initialize(reader.binaryReader)) | ||
| 65 | { | ||
| 66 | reader.invalidBundle = true; | ||
| 67 | } | ||
| 68 | |||
| 69 | return reader; | ||
| 70 | } | ||
| 71 | |||
| 72 | /// <summary> | ||
| 73 | /// Gets the UX container from the exe and extracts its contents to the output directory. | ||
| 74 | /// </summary> | ||
| 75 | /// <param name="outputDirectory">Directory to write extracted files to.</param> | ||
| 76 | /// <param name="tempDirectory">Scratch directory.</param> | ||
| 77 | /// <returns>True if successful, false otherwise</returns> | ||
| 78 | public bool ExtractUXContainer(string outputDirectory, string tempDirectory) | ||
| 79 | { | ||
| 80 | // No UX container to extract | ||
| 81 | if (this.UXAddress == 0 || this.UXSize == 0) | ||
| 82 | { | ||
| 83 | return false; | ||
| 84 | } | ||
| 85 | |||
| 86 | if (this.invalidBundle) | ||
| 87 | { | ||
| 88 | return false; | ||
| 89 | } | ||
| 90 | |||
| 91 | Directory.CreateDirectory(outputDirectory); | ||
| 92 | string tempCabPath = Path.Combine(tempDirectory, "ux.cab"); | ||
| 93 | string manifestOriginalPath = Path.Combine(outputDirectory, "0"); | ||
| 94 | string manifestPath = Path.Combine(outputDirectory, "manifest.xml"); | ||
| 95 | |||
| 96 | this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin); | ||
| 97 | using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) | ||
| 98 | { | ||
| 99 | BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize); | ||
| 100 | } | ||
| 101 | |||
| 102 | var cabinet = new Cabinet(tempCabPath); | ||
| 103 | cabinet.Extract(outputDirectory); | ||
| 104 | |||
| 105 | Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); | ||
| 106 | FileSystem.MoveFile(manifestOriginalPath, manifestPath); | ||
| 107 | |||
| 108 | XmlDocument document = new XmlDocument(); | ||
| 109 | document.Load(manifestPath); | ||
| 110 | XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable); | ||
| 111 | namespaceManager.AddNamespace("burn", BurnCommon.BurnNamespace); | ||
| 112 | XmlNodeList uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager); | ||
| 113 | XmlNodeList payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager); | ||
| 114 | |||
| 115 | foreach (XmlNode uxPayload in uxPayloads) | ||
| 116 | { | ||
| 117 | XmlNode sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath"); | ||
| 118 | XmlNode filePathNode = uxPayload.Attributes.GetNamedItem("FilePath"); | ||
| 119 | |||
| 120 | string sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value); | ||
| 121 | string destinationPath = Path.Combine(outputDirectory, filePathNode.Value); | ||
| 122 | |||
| 123 | Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); | ||
| 124 | FileSystem.MoveFile(sourcePath, destinationPath); | ||
| 125 | } | ||
| 126 | |||
| 127 | foreach (XmlNode payload in payloads) | ||
| 128 | { | ||
| 129 | XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath"); | ||
| 130 | XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath"); | ||
| 131 | XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging"); | ||
| 132 | |||
| 133 | string sourcePath = sourcePathNode.Value; | ||
| 134 | string destinationPath = filePathNode.Value; | ||
| 135 | string packaging = packagingNode.Value; | ||
| 136 | |||
| 137 | if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase)) | ||
| 138 | { | ||
| 139 | this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath)); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | return true; | ||
| 144 | } | ||
| 145 | |||
| 146 | internal void ExtractUXContainer(string uxExtractPath, object intermediateFolder) | ||
| 147 | { | ||
| 148 | throw new NotImplementedException(); | ||
| 149 | } | ||
| 150 | |||
| 151 | /// <summary> | ||
| 152 | /// Gets the attached container from the exe and extracts its contents to the output directory. | ||
| 153 | /// </summary> | ||
| 154 | /// <param name="outputDirectory">Directory to write extracted files to.</param> | ||
| 155 | /// <param name="tempDirectory">Scratch directory.</param> | ||
| 156 | /// <returns>True if successful, false otherwise</returns> | ||
| 157 | public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory) | ||
| 158 | { | ||
| 159 | // No attached container to extract | ||
| 160 | if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0) | ||
| 161 | { | ||
| 162 | return false; | ||
| 163 | } | ||
| 164 | |||
| 165 | if (this.invalidBundle) | ||
| 166 | { | ||
| 167 | return false; | ||
| 168 | } | ||
| 169 | |||
| 170 | Directory.CreateDirectory(outputDirectory); | ||
| 171 | string tempCabPath = Path.Combine(tempDirectory, "attached.cab"); | ||
| 172 | |||
| 173 | this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin); | ||
| 174 | using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) | ||
| 175 | { | ||
| 176 | BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize); | ||
| 177 | } | ||
| 178 | |||
| 179 | var cabinet = new Cabinet(tempCabPath); | ||
| 180 | cabinet.Extract(outputDirectory); | ||
| 181 | |||
| 182 | foreach (DictionaryEntry entry in this.attachedContainerPayloadNames) | ||
| 183 | { | ||
| 184 | string sourcePath = Path.Combine(outputDirectory, (string)entry.Key); | ||
| 185 | string destinationPath = Path.Combine(outputDirectory, (string)entry.Value); | ||
| 186 | |||
| 187 | Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); | ||
| 188 | FileSystem.MoveFile(sourcePath, destinationPath); | ||
| 189 | } | ||
| 190 | |||
| 191 | return true; | ||
| 192 | } | ||
| 193 | |||
| 194 | /// <summary> | ||
| 195 | /// Dispose object. | ||
| 196 | /// </summary> | ||
| 197 | /// <param name="disposing">True when releasing managed objects.</param> | ||
| 198 | protected override void Dispose(bool disposing) | ||
| 199 | { | ||
| 200 | if (!this.disposed) | ||
| 201 | { | ||
| 202 | if (disposing && this.binaryReader != null) | ||
| 203 | { | ||
| 204 | this.binaryReader.Close(); | ||
| 205 | this.binaryReader = null; | ||
| 206 | } | ||
| 207 | |||
| 208 | this.disposed = true; | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs new file mode 100644 index 00000000..2d16d11c --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs | |||
| @@ -0,0 +1,245 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | /// <summary> | ||
| 12 | /// Burn PE writer for the WiX toolset. | ||
| 13 | /// </summary> | ||
| 14 | /// <remarks>This class encapsulates reading/writing to a stub EXE for | ||
| 15 | /// creating bundled/chained setup packages.</remarks> | ||
| 16 | /// <example> | ||
| 17 | /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid)) | ||
| 18 | /// { | ||
| 19 | /// writer.AppendContainer(file1, BurnWriter.Container.UX); | ||
| 20 | /// writer.AppendContainer(file2, BurnWriter.Container.Attached); | ||
| 21 | /// } | ||
| 22 | /// </example> | ||
| 23 | internal class BurnWriter : BurnCommon | ||
| 24 | { | ||
| 25 | private bool disposed; | ||
| 26 | private bool invalidBundle; | ||
| 27 | private BinaryWriter binaryWriter; | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// Creates a BurnWriter for re-writing a PE file. | ||
| 31 | /// </summary> | ||
| 32 | /// <param name="messaging"></param> | ||
| 33 | /// <param name="fileExe">File to modify in-place.</param> | ||
| 34 | private BurnWriter(IMessaging messaging, string fileExe) | ||
| 35 | : base(messaging, fileExe) | ||
| 36 | { | ||
| 37 | } | ||
| 38 | |||
| 39 | /// <summary> | ||
| 40 | /// Opens a Burn writer. | ||
| 41 | /// </summary> | ||
| 42 | /// <param name="messaging"></param> | ||
| 43 | /// <param name="fileExe">Path to file.</param> | ||
| 44 | /// <returns>Burn writer.</returns> | ||
| 45 | public static BurnWriter Open(IMessaging messaging, string fileExe) | ||
| 46 | { | ||
| 47 | BurnWriter writer = new BurnWriter(messaging, fileExe); | ||
| 48 | |||
| 49 | using (BinaryReader binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))) | ||
| 50 | { | ||
| 51 | if (!writer.Initialize(binaryReader)) | ||
| 52 | { | ||
| 53 | writer.invalidBundle = true; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | if (!writer.invalidBundle) | ||
| 58 | { | ||
| 59 | writer.binaryWriter = new BinaryWriter(File.Open(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete)); | ||
| 60 | } | ||
| 61 | |||
| 62 | return writer; | ||
| 63 | } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Update the ".wixburn" section data. | ||
| 67 | /// </summary> | ||
| 68 | /// <param name="stubSize">Size of the stub engine "burn.exe".</param> | ||
| 69 | /// <param name="bundleId">Unique identifier for this bundle.</param> | ||
| 70 | /// <returns></returns> | ||
| 71 | public bool InitializeBundleSectionData(long stubSize, string bundleId) | ||
| 72 | { | ||
| 73 | if (this.invalidBundle) | ||
| 74 | { | ||
| 75 | return false; | ||
| 76 | } | ||
| 77 | |||
| 78 | var bundleGuid = Guid.Parse(bundleId); | ||
| 79 | |||
| 80 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); | ||
| 81 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); | ||
| 82 | |||
| 83 | this.Messaging.Write(VerboseMessages.BundleGuid(bundleId)); | ||
| 84 | this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); | ||
| 85 | this.binaryWriter.Write(bundleGuid.ToByteArray()); | ||
| 86 | |||
| 87 | this.StubSize = (uint)stubSize; | ||
| 88 | |||
| 89 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize); | ||
| 90 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0); | ||
| 91 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0); | ||
| 92 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0); | ||
| 93 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now. | ||
| 94 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0); | ||
| 95 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_UXSIZE, 0); | ||
| 96 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE, 0); | ||
| 97 | this.binaryWriter.BaseStream.Flush(); | ||
| 98 | |||
| 99 | this.EngineSize = this.StubSize; | ||
| 100 | |||
| 101 | return true; | ||
| 102 | } | ||
| 103 | |||
| 104 | /// <summary> | ||
| 105 | /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. | ||
| 106 | /// </summary> | ||
| 107 | /// <param name="fileContainer">File path to append to the current exe.</param> | ||
| 108 | /// <param name="container">Container section represented by the fileContainer.</param> | ||
| 109 | /// <returns>true if the container data is successfully appended; false otherwise</returns> | ||
| 110 | public bool AppendContainer(string fileContainer, BurnCommon.Container container) | ||
| 111 | { | ||
| 112 | using (FileStream reader = File.OpenRead(fileContainer)) | ||
| 113 | { | ||
| 114 | return this.AppendContainer(reader, reader.Length, container); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | /// <summary> | ||
| 119 | /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. | ||
| 120 | /// </summary> | ||
| 121 | /// <param name="containerStream">File stream to append to the current exe.</param> | ||
| 122 | /// <param name="containerSize">Size of container to append.</param> | ||
| 123 | /// <param name="container">Container section represented by the fileContainer.</param> | ||
| 124 | /// <returns>true if the container data is successfully appended; false otherwise</returns> | ||
| 125 | public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container) | ||
| 126 | { | ||
| 127 | UInt32 burnSectionCount = 0; | ||
| 128 | UInt32 burnSectionOffsetSize = 0; | ||
| 129 | |||
| 130 | switch (container) | ||
| 131 | { | ||
| 132 | case Container.UX: | ||
| 133 | burnSectionCount = 1; | ||
| 134 | burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE; | ||
| 135 | // TODO: verify that the size in the section data is 0 or the same size. | ||
| 136 | this.EngineSize += (uint)containerSize; | ||
| 137 | this.UXSize = (uint)containerSize; | ||
| 138 | break; | ||
| 139 | |||
| 140 | case Container.Attached: | ||
| 141 | burnSectionCount = 2; | ||
| 142 | burnSectionOffsetSize = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE; | ||
| 143 | // TODO: verify that the size in the section data is 0 or the same size. | ||
| 144 | this.AttachedContainerSize = (uint)containerSize; | ||
| 145 | break; | ||
| 146 | |||
| 147 | default: | ||
| 148 | Debug.Assert(false); | ||
| 149 | return false; | ||
| 150 | } | ||
| 151 | |||
| 152 | return this.AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); | ||
| 153 | } | ||
| 154 | |||
| 155 | public void RememberThenResetSignature() | ||
| 156 | { | ||
| 157 | if (this.invalidBundle) | ||
| 158 | { | ||
| 159 | return; | ||
| 160 | } | ||
| 161 | |||
| 162 | this.OriginalChecksum = this.Checksum; | ||
| 163 | this.OriginalSignatureOffset = this.SignatureOffset; | ||
| 164 | this.OriginalSignatureSize = this.SignatureSize; | ||
| 165 | |||
| 166 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum); | ||
| 167 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset); | ||
| 168 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize); | ||
| 169 | |||
| 170 | this.Checksum = 0; | ||
| 171 | this.SignatureOffset = 0; | ||
| 172 | this.SignatureSize = 0; | ||
| 173 | |||
| 174 | this.WriteToOffset(this.checksumOffset, this.Checksum); | ||
| 175 | this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset); | ||
| 176 | this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize); | ||
| 177 | } | ||
| 178 | |||
| 179 | /// <summary> | ||
| 180 | /// Dispose object. | ||
| 181 | /// </summary> | ||
| 182 | /// <param name="disposing">True when releasing managed objects.</param> | ||
| 183 | protected override void Dispose(bool disposing) | ||
| 184 | { | ||
| 185 | if (!this.disposed) | ||
| 186 | { | ||
| 187 | if (disposing && this.binaryWriter != null) | ||
| 188 | { | ||
| 189 | this.binaryWriter.Close(); | ||
| 190 | this.binaryWriter = null; | ||
| 191 | } | ||
| 192 | |||
| 193 | this.disposed = true; | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | /// <summary> | ||
| 198 | /// Appends a container to the exe and updates the ".wixburn" section data to point to it. | ||
| 199 | /// </summary> | ||
| 200 | /// <param name="containerStream">File stream to append to the current exe.</param> | ||
| 201 | /// <param name="containerSize">Size of the container.</param> | ||
| 202 | /// <param name="burnSectionOffsetSize">Offset of size field for this container in ".wixburn" section data.</param> | ||
| 203 | /// <param name="burnSectionCount">Number of Burn sections.</param> | ||
| 204 | /// <returns>true if the container data is successfully appended; false otherwise</returns> | ||
| 205 | private bool AppendContainer(Stream containerStream, UInt32 containerSize, UInt32 burnSectionOffsetSize, UInt32 burnSectionCount) | ||
| 206 | { | ||
| 207 | if (this.invalidBundle) | ||
| 208 | { | ||
| 209 | return false; | ||
| 210 | } | ||
| 211 | |||
| 212 | // Update the ".wixburn" section data | ||
| 213 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount); | ||
| 214 | this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize); | ||
| 215 | |||
| 216 | // Append the container to the end of the existing bits. | ||
| 217 | this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End); | ||
| 218 | BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize); | ||
| 219 | this.binaryWriter.BaseStream.Flush(); | ||
| 220 | |||
| 221 | return true; | ||
| 222 | } | ||
| 223 | |||
| 224 | /// <summary> | ||
| 225 | /// Writes the value to an offset in the Burn section data. | ||
| 226 | /// </summary> | ||
| 227 | /// <param name="offset">Offset in to the Burn section data.</param> | ||
| 228 | /// <param name="value">Value to write.</param> | ||
| 229 | private void WriteToBurnSectionOffset(uint offset, uint value) | ||
| 230 | { | ||
| 231 | this.WriteToOffset(this.wixburnDataOffset + offset, value); | ||
| 232 | } | ||
| 233 | |||
| 234 | /// <summary> | ||
| 235 | /// Writes the value to an offset in the Burn stub. | ||
| 236 | /// </summary> | ||
| 237 | /// <param name="offset">Offset in to the Burn stub.</param> | ||
| 238 | /// <param name="value">Value to write.</param> | ||
| 239 | private void WriteToOffset(uint offset, uint value) | ||
| 240 | { | ||
| 241 | this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin); | ||
| 242 | this.binaryWriter.Write(value); | ||
| 243 | } | ||
| 244 | } | ||
| 245 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs new file mode 100644 index 00000000..a0ee606d --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs | |||
| @@ -0,0 +1,290 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Text; | ||
| 11 | using System.Xml; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Burn; | ||
| 14 | using WixToolset.Data.Symbols; | ||
| 15 | |||
| 16 | internal class CreateBootstrapperApplicationManifestCommand | ||
| 17 | { | ||
| 18 | public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable<PackageFacade> chainPackages, int lastUXPayloadIndex, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols, Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> packagesPayloads, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) | ||
| 19 | { | ||
| 20 | this.Section = section; | ||
| 21 | this.BundleSymbol = bundleSymbol; | ||
| 22 | this.ChainPackages = chainPackages; | ||
| 23 | this.LastUXPayloadIndex = lastUXPayloadIndex; | ||
| 24 | this.Payloads = payloadSymbols; | ||
| 25 | this.PackagesPayloads = packagesPayloads; | ||
| 26 | this.IntermediateFolder = intermediateFolder; | ||
| 27 | this.InternalBurnBackendHelper = internalBurnBackendHelper; | ||
| 28 | } | ||
| 29 | |||
| 30 | private IntermediateSection Section { get; } | ||
| 31 | |||
| 32 | private WixBundleSymbol BundleSymbol { get; } | ||
| 33 | |||
| 34 | private IEnumerable<PackageFacade> ChainPackages { get; } | ||
| 35 | |||
| 36 | private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } | ||
| 37 | |||
| 38 | private int LastUXPayloadIndex { get; } | ||
| 39 | |||
| 40 | private Dictionary<string, WixBundlePayloadSymbol> Payloads { get; } | ||
| 41 | |||
| 42 | private Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> PackagesPayloads { get; } | ||
| 43 | |||
| 44 | private string IntermediateFolder { get; } | ||
| 45 | |||
| 46 | public WixBundlePayloadSymbol BootstrapperApplicationManifestPayloadRow { get; private set; } | ||
| 47 | |||
| 48 | public string OutputPath { get; private set; } | ||
| 49 | |||
| 50 | public void Execute() | ||
| 51 | { | ||
| 52 | this.OutputPath = this.CreateBootstrapperApplicationManifest(); | ||
| 53 | |||
| 54 | this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(this.OutputPath); | ||
| 55 | } | ||
| 56 | |||
| 57 | private string CreateBootstrapperApplicationManifest() | ||
| 58 | { | ||
| 59 | var path = Path.Combine(this.IntermediateFolder, "wix-badata.xml"); | ||
| 60 | |||
| 61 | Directory.CreateDirectory(Path.GetDirectoryName(path)); | ||
| 62 | |||
| 63 | using (var writer = new XmlTextWriter(path, Encoding.Unicode)) | ||
| 64 | { | ||
| 65 | writer.Formatting = Formatting.Indented; | ||
| 66 | writer.WriteStartDocument(); | ||
| 67 | writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace); | ||
| 68 | |||
| 69 | this.WriteBundleInfo(writer); | ||
| 70 | |||
| 71 | this.WritePackageInfo(writer); | ||
| 72 | |||
| 73 | this.WriteFeatureInfo(writer); | ||
| 74 | |||
| 75 | this.WritePayloadInfo(writer); | ||
| 76 | |||
| 77 | this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer); | ||
| 78 | |||
| 79 | writer.WriteEndElement(); | ||
| 80 | writer.WriteEndDocument(); | ||
| 81 | } | ||
| 82 | |||
| 83 | return path; | ||
| 84 | } | ||
| 85 | |||
| 86 | private void WriteBundleInfo(XmlTextWriter writer) | ||
| 87 | { | ||
| 88 | writer.WriteStartElement("WixBundleProperties"); | ||
| 89 | |||
| 90 | writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); | ||
| 91 | writer.WriteAttributeString("LogPathVariable", this.BundleSymbol.LogPathVariable); | ||
| 92 | writer.WriteAttributeString("Compressed", this.BundleSymbol.Compressed == true ? "yes" : "no"); | ||
| 93 | writer.WriteAttributeString("Id", this.BundleSymbol.BundleId.ToUpperInvariant()); | ||
| 94 | writer.WriteAttributeString("UpgradeCode", this.BundleSymbol.UpgradeCode); | ||
| 95 | writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); | ||
| 96 | |||
| 97 | writer.WriteEndElement(); | ||
| 98 | } | ||
| 99 | |||
| 100 | private void WritePackageInfo(XmlTextWriter writer) | ||
| 101 | { | ||
| 102 | foreach (var package in this.ChainPackages) | ||
| 103 | { | ||
| 104 | if (!this.PackagesPayloads.TryGetValue(package.PackageId, out var payloads)) | ||
| 105 | { | ||
| 106 | continue; | ||
| 107 | } | ||
| 108 | |||
| 109 | var packagePayload = payloads[package.PackageSymbol.PayloadRef]; | ||
| 110 | |||
| 111 | var size = package.PackageSymbol.Size.ToString(CultureInfo.InvariantCulture); | ||
| 112 | |||
| 113 | writer.WriteStartElement("WixPackageProperties"); | ||
| 114 | |||
| 115 | writer.WriteAttributeString("Package", package.PackageId); | ||
| 116 | writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == true ? "yes" : "no"); | ||
| 117 | |||
| 118 | if (!String.IsNullOrEmpty(package.PackageSymbol.DisplayName)) | ||
| 119 | { | ||
| 120 | writer.WriteAttributeString("DisplayName", package.PackageSymbol.DisplayName); | ||
| 121 | } | ||
| 122 | |||
| 123 | if (!String.IsNullOrEmpty(package.PackageSymbol.Description)) | ||
| 124 | { | ||
| 125 | writer.WriteAttributeString("Description", package.PackageSymbol.Description); | ||
| 126 | } | ||
| 127 | |||
| 128 | writer.WriteAttributeString("DownloadSize", size); | ||
| 129 | writer.WriteAttributeString("PackageSize", size); | ||
| 130 | writer.WriteAttributeString("InstalledSize", package.PackageSymbol.InstallSize?.ToString(CultureInfo.InvariantCulture) ?? size); | ||
| 131 | writer.WriteAttributeString("PackageType", package.PackageSymbol.Type.ToString()); | ||
| 132 | writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); | ||
| 133 | writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); | ||
| 134 | writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); | ||
| 135 | writer.WriteAttributeString("Compressed", packagePayload.Packaging == PackagingType.Embedded ? "yes" : "no"); | ||
| 136 | |||
| 137 | if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) | ||
| 138 | { | ||
| 139 | if (!String.IsNullOrEmpty(msiPackage.ProductCode)) | ||
| 140 | { | ||
| 141 | writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); | ||
| 142 | } | ||
| 143 | |||
| 144 | if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) | ||
| 145 | { | ||
| 146 | writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) | ||
| 150 | { | ||
| 151 | if (!String.IsNullOrEmpty(mspPackage.PatchCode)) | ||
| 152 | { | ||
| 153 | writer.WriteAttributeString("ProductCode", mspPackage.PatchCode); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | if (!String.IsNullOrEmpty(package.PackageSymbol.Version)) | ||
| 158 | { | ||
| 159 | writer.WriteAttributeString("Version", package.PackageSymbol.Version); | ||
| 160 | } | ||
| 161 | |||
| 162 | if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) | ||
| 163 | { | ||
| 164 | writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); | ||
| 165 | } | ||
| 166 | |||
| 167 | switch (package.PackageSymbol.Cache) | ||
| 168 | { | ||
| 169 | case YesNoAlwaysType.No: | ||
| 170 | writer.WriteAttributeString("Cache", "remove"); | ||
| 171 | break; | ||
| 172 | case YesNoAlwaysType.Yes: | ||
| 173 | writer.WriteAttributeString("Cache", "keep"); | ||
| 174 | break; | ||
| 175 | case YesNoAlwaysType.Always: | ||
| 176 | writer.WriteAttributeString("Cache", "force"); | ||
| 177 | break; | ||
| 178 | } | ||
| 179 | |||
| 180 | writer.WriteEndElement(); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | private void WriteFeatureInfo(XmlTextWriter writer) | ||
| 185 | { | ||
| 186 | var featureSymbols = this.Section.Symbols.OfType<WixBundleMsiFeatureSymbol>(); | ||
| 187 | |||
| 188 | foreach (var featureSymbol in featureSymbols) | ||
| 189 | { | ||
| 190 | writer.WriteStartElement("WixPackageFeatureInfo"); | ||
| 191 | |||
| 192 | writer.WriteAttributeString("Package", featureSymbol.PackageRef); | ||
| 193 | writer.WriteAttributeString("Feature", featureSymbol.Name); | ||
| 194 | writer.WriteAttributeString("Size", featureSymbol.Size.ToString(CultureInfo.InvariantCulture)); | ||
| 195 | |||
| 196 | if (!String.IsNullOrEmpty(featureSymbol.Parent)) | ||
| 197 | { | ||
| 198 | writer.WriteAttributeString("Parent", featureSymbol.Parent); | ||
| 199 | } | ||
| 200 | |||
| 201 | if (!String.IsNullOrEmpty(featureSymbol.Title)) | ||
| 202 | { | ||
| 203 | writer.WriteAttributeString("Title", featureSymbol.Title); | ||
| 204 | } | ||
| 205 | |||
| 206 | if (!String.IsNullOrEmpty(featureSymbol.Description)) | ||
| 207 | { | ||
| 208 | writer.WriteAttributeString("Description", featureSymbol.Description); | ||
| 209 | } | ||
| 210 | |||
| 211 | writer.WriteAttributeString("Display", featureSymbol.Display.ToString(CultureInfo.InvariantCulture)); | ||
| 212 | writer.WriteAttributeString("Level", featureSymbol.Level.ToString(CultureInfo.InvariantCulture)); | ||
| 213 | writer.WriteAttributeString("Directory", featureSymbol.Directory); | ||
| 214 | writer.WriteAttributeString("Attributes", featureSymbol.Attributes.ToString(CultureInfo.InvariantCulture)); | ||
| 215 | |||
| 216 | writer.WriteEndElement(); | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | private void WritePayloadInfo(XmlTextWriter writer) | ||
| 221 | { | ||
| 222 | foreach (var kvp in this.PackagesPayloads.OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) | ||
| 223 | { | ||
| 224 | var packageId = kvp.Key; | ||
| 225 | var payloadsById = kvp.Value; | ||
| 226 | |||
| 227 | foreach (var payloadSymbol in payloadsById.Values.OrderBy(p => p.Id.Id, StringComparer.Ordinal)) | ||
| 228 | { | ||
| 229 | this.WritePayloadInfo(writer, payloadSymbol, packageId); | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | foreach (var payloadSymbol in this.Payloads.Values.Where(p => p.LayoutOnly).OrderBy(p => p.Id.Id, StringComparer.Ordinal)) | ||
| 234 | { | ||
| 235 | this.WritePayloadInfo(writer, payloadSymbol, null); | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | private void WritePayloadInfo(XmlTextWriter writer, WixBundlePayloadSymbol payloadSymbol, string packageId) | ||
| 240 | { | ||
| 241 | writer.WriteStartElement("WixPayloadProperties"); | ||
| 242 | |||
| 243 | if (!String.IsNullOrEmpty(packageId)) | ||
| 244 | { | ||
| 245 | writer.WriteAttributeString("Package", packageId); | ||
| 246 | } | ||
| 247 | |||
| 248 | writer.WriteAttributeString("Payload", payloadSymbol.Id.Id); | ||
| 249 | |||
| 250 | if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef)) | ||
| 251 | { | ||
| 252 | writer.WriteAttributeString("Container", payloadSymbol.ContainerRef); | ||
| 253 | } | ||
| 254 | |||
| 255 | writer.WriteAttributeString("Name", payloadSymbol.Name); | ||
| 256 | writer.WriteAttributeString("Size", payloadSymbol.FileSize.Value.ToString(CultureInfo.InvariantCulture)); | ||
| 257 | |||
| 258 | if (!String.IsNullOrEmpty(payloadSymbol.DownloadUrl)) | ||
| 259 | { | ||
| 260 | writer.WriteAttributeString("DownloadUrl", payloadSymbol.DownloadUrl); | ||
| 261 | } | ||
| 262 | |||
| 263 | writer.WriteEndElement(); | ||
| 264 | } | ||
| 265 | |||
| 266 | private WixBundlePayloadSymbol CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) | ||
| 267 | { | ||
| 268 | var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BADataFileName); | ||
| 269 | |||
| 270 | var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) | ||
| 271 | { | ||
| 272 | Name = BurnCommon.BADataFileName, | ||
| 273 | SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, | ||
| 274 | Compressed = true, | ||
| 275 | UnresolvedSourceFile = baManifestPath, | ||
| 276 | ContainerRef = BurnConstants.BurnUXContainerName, | ||
| 277 | EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), | ||
| 278 | Packaging = PackagingType.Embedded, | ||
| 279 | }); | ||
| 280 | |||
| 281 | var fileInfo = new FileInfo(baManifestPath); | ||
| 282 | |||
| 283 | symbol.FileSize = (int)fileInfo.Length; | ||
| 284 | |||
| 285 | symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); | ||
| 286 | |||
| 287 | return symbol; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs new file mode 100644 index 00000000..b802f556 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs | |||
| @@ -0,0 +1,325 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Reflection; | ||
| 9 | using System.Text; | ||
| 10 | using System.Xml; | ||
| 11 | using WixToolset.Core.Native; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Burn; | ||
| 14 | using WixToolset.Data.Symbols; | ||
| 15 | using WixToolset.Dtf.Resources; | ||
| 16 | using WixToolset.Extensibility.Data; | ||
| 17 | using WixToolset.Extensibility.Services; | ||
| 18 | |||
| 19 | internal class CreateBundleExeCommand | ||
| 20 | { | ||
| 21 | public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable<WixBundleContainerSymbol> containers) | ||
| 22 | { | ||
| 23 | this.Messaging = messaging; | ||
| 24 | this.BackendHelper = backendHelper; | ||
| 25 | this.IntermediateFolder = intermediateFolder; | ||
| 26 | this.OutputPath = outputPath; | ||
| 27 | this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; | ||
| 28 | this.BundleSymbol = bundleSymbol; | ||
| 29 | this.UXContainer = uxContainer; | ||
| 30 | this.Containers = containers; | ||
| 31 | } | ||
| 32 | |||
| 33 | public IFileTransfer Transfer { get; private set; } | ||
| 34 | |||
| 35 | private IMessaging Messaging { get; } | ||
| 36 | |||
| 37 | private IBackendHelper BackendHelper { get; } | ||
| 38 | |||
| 39 | private string IntermediateFolder { get; } | ||
| 40 | |||
| 41 | private string OutputPath { get; } | ||
| 42 | |||
| 43 | private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } | ||
| 44 | |||
| 45 | private WixBundleSymbol BundleSymbol { get; } | ||
| 46 | |||
| 47 | private WixBundleContainerSymbol UXContainer { get; } | ||
| 48 | |||
| 49 | private IEnumerable<WixBundleContainerSymbol> Containers { get; } | ||
| 50 | |||
| 51 | public void Execute() | ||
| 52 | { | ||
| 53 | var bundleFilename = Path.GetFileName(this.OutputPath); | ||
| 54 | |||
| 55 | // Copy the burn.exe to a writable location then mark it to be moved to its final build location. | ||
| 56 | |||
| 57 | var stubPlatform = this.BundleSymbol.Platform.ToString(); | ||
| 58 | var stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); | ||
| 59 | |||
| 60 | if (stubPlatform != "X86") | ||
| 61 | { | ||
| 62 | this.Messaging.Write(WarningMessages.ExperimentalBundlePlatform(stubPlatform)); | ||
| 63 | } | ||
| 64 | |||
| 65 | var bundleTempPath = Path.Combine(this.IntermediateFolder, bundleFilename); | ||
| 66 | |||
| 67 | this.Messaging.Write(VerboseMessages.GeneratingBundle(bundleTempPath, stubFile)); | ||
| 68 | |||
| 69 | if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) | ||
| 70 | { | ||
| 71 | this.Messaging.Write(ErrorMessages.InsecureBundleFilename(bundleFilename)); | ||
| 72 | } | ||
| 73 | |||
| 74 | this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleSymbol.SourceLineNumbers); | ||
| 75 | |||
| 76 | FileSystem.CopyFile(stubFile, bundleTempPath, allowHardlink: false); | ||
| 77 | File.SetAttributes(bundleTempPath, FileAttributes.Normal); | ||
| 78 | |||
| 79 | var windowsAssemblyVersion = GetWindowsAssemblyVersion(this.BundleSymbol); | ||
| 80 | |||
| 81 | var applicationManifestData = GenerateApplicationManifest(this.BundleSymbol, this.BootstrapperApplicationDllSymbol, this.OutputPath, windowsAssemblyVersion); | ||
| 82 | |||
| 83 | UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleSymbol, windowsAssemblyVersion, applicationManifestData); | ||
| 84 | |||
| 85 | // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers | ||
| 86 | // if they should be attached. | ||
| 87 | using (var writer = BurnWriter.Open(this.Messaging, bundleTempPath)) | ||
| 88 | { | ||
| 89 | var burnStubFile = new FileInfo(bundleTempPath); | ||
| 90 | writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleSymbol.BundleId); | ||
| 91 | |||
| 92 | // Always attach the UX container first | ||
| 93 | writer.AppendContainer(this.UXContainer.WorkingPath, BurnWriter.Container.UX); | ||
| 94 | |||
| 95 | // Now append all other attached containers | ||
| 96 | foreach (var container in this.Containers) | ||
| 97 | { | ||
| 98 | if (ContainerType.Attached == container.Type) | ||
| 99 | { | ||
| 100 | // The container was only created if it had payloads. | ||
| 101 | if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) | ||
| 102 | { | ||
| 103 | writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | private static byte[] GenerateApplicationManifest(WixBundleSymbol bundleSymbol, WixBootstrapperApplicationDllSymbol bootstrapperApplicationSymbol, string outputPath, Version windowsAssemblyVersion) | ||
| 111 | { | ||
| 112 | const string asmv1Namespace = "urn:schemas-microsoft-com:asm.v1"; | ||
| 113 | const string asmv3Namespace = "urn:schemas-microsoft-com:asm.v3"; | ||
| 114 | const string compatv1Namespace = "urn:schemas-microsoft-com:compatibility.v1"; | ||
| 115 | const string ws2005Namespace = "http://schemas.microsoft.com/SMI/2005/WindowsSettings"; | ||
| 116 | const string ws2016Namespace = "http://schemas.microsoft.com/SMI/2016/WindowsSettings"; | ||
| 117 | const string ws2017Namespace = "http://schemas.microsoft.com/SMI/2017/WindowsSettings"; | ||
| 118 | |||
| 119 | var bundleFileName = Path.GetFileName(outputPath); | ||
| 120 | var bundleAssemblyVersion = windowsAssemblyVersion.ToString(); | ||
| 121 | var bundlePlatform = bundleSymbol.Platform == Platform.X64 ? "amd64" : bundleSymbol.Platform.ToString().ToLower(); | ||
| 122 | var bundleDescription = bundleSymbol.Name; | ||
| 123 | |||
| 124 | using (var memoryStream = new MemoryStream()) | ||
| 125 | using (var writer = new XmlTextWriter(memoryStream, Encoding.UTF8)) | ||
| 126 | { | ||
| 127 | writer.WriteStartDocument(); | ||
| 128 | |||
| 129 | writer.WriteStartElement("assembly", asmv1Namespace); | ||
| 130 | writer.WriteAttributeString("manifestVersion", "1.0"); | ||
| 131 | |||
| 132 | writer.WriteStartElement("assemblyIdentity"); | ||
| 133 | writer.WriteAttributeString("name", bundleFileName); | ||
| 134 | writer.WriteAttributeString("version", bundleAssemblyVersion); | ||
| 135 | writer.WriteAttributeString("processorArchitecture", bundlePlatform); | ||
| 136 | writer.WriteAttributeString("type", "win32"); | ||
| 137 | writer.WriteEndElement(); // </assemblyIdentity> | ||
| 138 | |||
| 139 | if (!String.IsNullOrEmpty(bundleDescription)) | ||
| 140 | { | ||
| 141 | writer.WriteStartElement("description"); | ||
| 142 | writer.WriteString(bundleDescription); | ||
| 143 | writer.WriteEndElement(); | ||
| 144 | } | ||
| 145 | |||
| 146 | writer.WriteStartElement("dependency"); | ||
| 147 | writer.WriteStartElement("dependentAssembly"); | ||
| 148 | writer.WriteStartElement("assemblyIdentity"); | ||
| 149 | writer.WriteAttributeString("name", "Microsoft.Windows.Common-Controls"); | ||
| 150 | writer.WriteAttributeString("version", "6.0.0.0"); | ||
| 151 | writer.WriteAttributeString("processorArchitecture", bundlePlatform); | ||
| 152 | writer.WriteAttributeString("publicKeyToken", "6595b64144ccf1df"); | ||
| 153 | writer.WriteAttributeString("language", "*"); | ||
| 154 | writer.WriteAttributeString("type", "win32"); | ||
| 155 | writer.WriteEndElement(); // </assemblyIdentity> | ||
| 156 | writer.WriteEndElement(); // </dependentAssembly> | ||
| 157 | writer.WriteEndElement(); // </dependency> | ||
| 158 | |||
| 159 | writer.WriteStartElement("compatibility", compatv1Namespace); | ||
| 160 | writer.WriteStartElement("application"); | ||
| 161 | |||
| 162 | writer.WriteStartElement("supportedOS"); | ||
| 163 | writer.WriteAttributeString("Id", "{e2011457-1546-43c5-a5fe-008deee3d3f0}"); // Windows Vista | ||
| 164 | writer.WriteEndElement(); | ||
| 165 | writer.WriteStartElement("supportedOS"); | ||
| 166 | writer.WriteAttributeString("Id", "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"); // Windows 7 | ||
| 167 | writer.WriteEndElement(); | ||
| 168 | writer.WriteStartElement("supportedOS"); | ||
| 169 | writer.WriteAttributeString("Id", "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"); // Windows 8 | ||
| 170 | writer.WriteEndElement(); | ||
| 171 | writer.WriteStartElement("supportedOS"); | ||
| 172 | writer.WriteAttributeString("Id", "{1f676c76-80e1-4239-95bb-83d0f6d0da78}"); // Windows 8.1 | ||
| 173 | writer.WriteEndElement(); | ||
| 174 | writer.WriteStartElement("supportedOS"); | ||
| 175 | writer.WriteAttributeString("Id", "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"); // Windows 10 | ||
| 176 | writer.WriteEndElement(); | ||
| 177 | |||
| 178 | writer.WriteEndElement(); // </application> | ||
| 179 | writer.WriteEndElement(); // </compatibility> | ||
| 180 | |||
| 181 | writer.WriteStartElement("trustInfo", asmv3Namespace); | ||
| 182 | writer.WriteStartElement("security"); | ||
| 183 | writer.WriteStartElement("requestedPrivileges"); | ||
| 184 | writer.WriteStartElement("requestedExecutionLevel"); | ||
| 185 | writer.WriteAttributeString("level", "asInvoker"); | ||
| 186 | writer.WriteAttributeString("uiAccess", "false"); | ||
| 187 | writer.WriteEndElement(); // </requestedExecutionLevel> | ||
| 188 | writer.WriteEndElement(); // </requestedPrivileges> | ||
| 189 | writer.WriteEndElement(); // </security> | ||
| 190 | writer.WriteEndElement(); // </trustInfo> | ||
| 191 | |||
| 192 | if (bootstrapperApplicationSymbol.DpiAwareness != WixBootstrapperApplicationDpiAwarenessType.Unaware) | ||
| 193 | { | ||
| 194 | string dpiAwareValue = null; | ||
| 195 | string dpiAwarenessValue = null; | ||
| 196 | string gdiScalingValue = null; | ||
| 197 | |||
| 198 | switch(bootstrapperApplicationSymbol.DpiAwareness) | ||
| 199 | { | ||
| 200 | case WixBootstrapperApplicationDpiAwarenessType.GdiScaled: | ||
| 201 | gdiScalingValue = "true"; | ||
| 202 | break; | ||
| 203 | case WixBootstrapperApplicationDpiAwarenessType.PerMonitor: | ||
| 204 | dpiAwareValue = "true/pm"; | ||
| 205 | break; | ||
| 206 | case WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2: | ||
| 207 | dpiAwareValue = "true/pm"; | ||
| 208 | dpiAwarenessValue = "PerMonitorV2, PerMonitor"; | ||
| 209 | break; | ||
| 210 | case WixBootstrapperApplicationDpiAwarenessType.System: | ||
| 211 | dpiAwareValue = "true"; | ||
| 212 | break; | ||
| 213 | } | ||
| 214 | |||
| 215 | writer.WriteStartElement("application", asmv3Namespace); | ||
| 216 | writer.WriteStartElement("windowsSettings"); | ||
| 217 | |||
| 218 | if (dpiAwareValue != null) | ||
| 219 | { | ||
| 220 | writer.WriteStartElement("dpiAware", ws2005Namespace); | ||
| 221 | writer.WriteString(dpiAwareValue); | ||
| 222 | writer.WriteEndElement(); | ||
| 223 | } | ||
| 224 | |||
| 225 | if (dpiAwarenessValue != null) | ||
| 226 | { | ||
| 227 | writer.WriteStartElement("dpiAwareness", ws2016Namespace); | ||
| 228 | writer.WriteString(dpiAwarenessValue); | ||
| 229 | writer.WriteEndElement(); | ||
| 230 | } | ||
| 231 | |||
| 232 | if (gdiScalingValue != null) | ||
| 233 | { | ||
| 234 | writer.WriteStartElement("gdiScaling", ws2017Namespace); | ||
| 235 | writer.WriteString(gdiScalingValue); | ||
| 236 | writer.WriteEndElement(); | ||
| 237 | } | ||
| 238 | |||
| 239 | writer.WriteEndElement(); // </windowSettings> | ||
| 240 | writer.WriteEndElement(); // </application> | ||
| 241 | } | ||
| 242 | |||
| 243 | writer.WriteEndDocument(); // </assembly> | ||
| 244 | writer.Close(); | ||
| 245 | |||
| 246 | return memoryStream.ToArray(); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | private static Version GetWindowsAssemblyVersion(WixBundleSymbol bundleSymbol) | ||
| 251 | { | ||
| 252 | // Ensure the bundle info provides a full four part version. | ||
| 253 | var fourPartVersion = new Version(bundleSymbol.Version); | ||
| 254 | var major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; | ||
| 255 | var minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; | ||
| 256 | var build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; | ||
| 257 | var revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; | ||
| 258 | |||
| 259 | if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) | ||
| 260 | { | ||
| 261 | throw new WixException(ErrorMessages.InvalidModuleOrBundleVersion(bundleSymbol.SourceLineNumbers, "Bundle", bundleSymbol.Version)); | ||
| 262 | } | ||
| 263 | |||
| 264 | return new Version(major, minor, build, revision); | ||
| 265 | } | ||
| 266 | |||
| 267 | private static void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleSymbol bundleInfo, Version windowsAssemblyVersion, byte[] applicationManifestData) | ||
| 268 | { | ||
| 269 | const int burnLocale = 1033; | ||
| 270 | var resources = new Dtf.Resources.ResourceCollection(); | ||
| 271 | var version = new Dtf.Resources.VersionResource("#1", burnLocale); | ||
| 272 | |||
| 273 | version.Load(bundleTempPath); | ||
| 274 | resources.Add(version); | ||
| 275 | |||
| 276 | version.FileVersion = windowsAssemblyVersion; | ||
| 277 | version.ProductVersion = windowsAssemblyVersion; | ||
| 278 | |||
| 279 | var strings = version[burnLocale] ?? version.Add(burnLocale); | ||
| 280 | strings["LegalCopyright"] = bundleInfo.Copyright; | ||
| 281 | strings["OriginalFilename"] = Path.GetFileName(outputPath); | ||
| 282 | strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. | ||
| 283 | strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. | ||
| 284 | |||
| 285 | if (!String.IsNullOrEmpty(bundleInfo.Name)) | ||
| 286 | { | ||
| 287 | strings["ProductName"] = bundleInfo.Name; | ||
| 288 | strings["FileDescription"] = bundleInfo.Name; | ||
| 289 | } | ||
| 290 | |||
| 291 | if (!String.IsNullOrEmpty(bundleInfo.Manufacturer)) | ||
| 292 | { | ||
| 293 | strings["CompanyName"] = bundleInfo.Manufacturer; | ||
| 294 | } | ||
| 295 | else | ||
| 296 | { | ||
| 297 | strings["CompanyName"] = String.Empty; | ||
| 298 | } | ||
| 299 | |||
| 300 | if (!String.IsNullOrEmpty(bundleInfo.IconSourceFile)) | ||
| 301 | { | ||
| 302 | var iconGroup = new Dtf.Resources.GroupIconResource("#1", burnLocale); | ||
| 303 | iconGroup.ReadFromFile(bundleInfo.IconSourceFile); | ||
| 304 | resources.Add(iconGroup); | ||
| 305 | |||
| 306 | foreach (var icon in iconGroup.Icons) | ||
| 307 | { | ||
| 308 | resources.Add(icon); | ||
| 309 | } | ||
| 310 | } | ||
| 311 | |||
| 312 | if (!String.IsNullOrEmpty(bundleInfo.SplashScreenSourceFile)) | ||
| 313 | { | ||
| 314 | var bitmap = new Dtf.Resources.BitmapResource("#1", burnLocale); | ||
| 315 | bitmap.ReadFromFile(bundleInfo.SplashScreenSourceFile); | ||
| 316 | resources.Add(bitmap); | ||
| 317 | } | ||
| 318 | |||
| 319 | var manifestResource = new Resource(ResourceType.Manifest, "#1", burnLocale, applicationManifestData); | ||
| 320 | resources.Add(manifestResource); | ||
| 321 | |||
| 322 | resources.Save(bundleTempPath); | ||
| 323 | } | ||
| 324 | } | ||
| 325 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs new file mode 100644 index 00000000..e587413e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Globalization; | ||
| 7 | using System.IO; | ||
| 8 | using System.Text; | ||
| 9 | using System.Xml; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Burn; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | |||
| 14 | internal class CreateBundleExtensionManifestCommand | ||
| 15 | { | ||
| 16 | public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, int lastUXPayloadIndex, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) | ||
| 17 | { | ||
| 18 | this.Section = section; | ||
| 19 | this.BundleSymbol = bundleSymbol; | ||
| 20 | this.LastUXPayloadIndex = lastUXPayloadIndex; | ||
| 21 | this.IntermediateFolder = intermediateFolder; | ||
| 22 | this.InternalBurnBackendHelper = internalBurnBackendHelper; | ||
| 23 | } | ||
| 24 | |||
| 25 | private IntermediateSection Section { get; } | ||
| 26 | |||
| 27 | private WixBundleSymbol BundleSymbol { get; } | ||
| 28 | |||
| 29 | private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } | ||
| 30 | |||
| 31 | private int LastUXPayloadIndex { get; } | ||
| 32 | |||
| 33 | private string IntermediateFolder { get; } | ||
| 34 | |||
| 35 | public WixBundlePayloadSymbol BundleExtensionManifestPayloadRow { get; private set; } | ||
| 36 | |||
| 37 | public string OutputPath { get; private set; } | ||
| 38 | |||
| 39 | public void Execute() | ||
| 40 | { | ||
| 41 | this.OutputPath = this.CreateBundleExtensionManifest(); | ||
| 42 | |||
| 43 | this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(this.OutputPath); | ||
| 44 | } | ||
| 45 | |||
| 46 | private string CreateBundleExtensionManifest() | ||
| 47 | { | ||
| 48 | var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml"); | ||
| 49 | |||
| 50 | Directory.CreateDirectory(Path.GetDirectoryName(path)); | ||
| 51 | |||
| 52 | using (var writer = new XmlTextWriter(path, Encoding.Unicode)) | ||
| 53 | { | ||
| 54 | writer.Formatting = Formatting.Indented; | ||
| 55 | writer.WriteStartDocument(); | ||
| 56 | writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); | ||
| 57 | |||
| 58 | this.InternalBurnBackendHelper.WriteBundleExtensionData(writer); | ||
| 59 | |||
| 60 | writer.WriteEndElement(); | ||
| 61 | writer.WriteEndDocument(); | ||
| 62 | } | ||
| 63 | |||
| 64 | return path; | ||
| 65 | } | ||
| 66 | |||
| 67 | private WixBundlePayloadSymbol CreateBundleExtensionManifestPayloadRow(string bextManifestPath) | ||
| 68 | { | ||
| 69 | var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName); | ||
| 70 | |||
| 71 | this.Section.AddSymbol(new WixGroupSymbol(this.BundleSymbol.SourceLineNumbers) | ||
| 72 | { | ||
| 73 | ParentType = ComplexReferenceParentType.Container, | ||
| 74 | ParentId = BurnConstants.BurnUXContainerName, | ||
| 75 | ChildType = ComplexReferenceChildType.Payload, | ||
| 76 | ChildId = generatedId | ||
| 77 | }); | ||
| 78 | |||
| 79 | var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) | ||
| 80 | { | ||
| 81 | Name = BurnCommon.BundleExtensionDataFileName, | ||
| 82 | SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath }, | ||
| 83 | Compressed = true, | ||
| 84 | UnresolvedSourceFile = bextManifestPath, | ||
| 85 | ContainerRef = BurnConstants.BurnUXContainerName, | ||
| 86 | EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), | ||
| 87 | Packaging = PackagingType.Embedded, | ||
| 88 | }); | ||
| 89 | |||
| 90 | var fileInfo = new FileInfo(bextManifestPath); | ||
| 91 | |||
| 92 | symbol.FileSize = (int)fileInfo.Length; | ||
| 93 | |||
| 94 | symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); | ||
| 95 | |||
| 96 | return symbol; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs new file mode 100644 index 00000000..5655d23d --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs | |||
| @@ -0,0 +1,700 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using System.Text; | ||
| 12 | using System.Xml; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Burn; | ||
| 15 | using WixToolset.Data.Symbols; | ||
| 16 | using WixToolset.Extensibility; | ||
| 17 | using WixToolset.Extensibility.Services; | ||
| 18 | |||
| 19 | internal class CreateBurnManifestCommand | ||
| 20 | { | ||
| 21 | public CreateBurnManifestCommand(string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable<WixBundleContainerSymbol> containers, WixChainSymbol chainSymbol, IEnumerable<PackageFacade> orderedPackages, IEnumerable<WixBundleRollbackBoundarySymbol> boundaries, IEnumerable<WixBundlePayloadSymbol> uxPayloads, Dictionary<string, WixBundlePayloadSymbol> allPayloadsById, Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> packagesPayloads, IEnumerable<ISearchFacade> orderedSearches, string intermediateFolder) | ||
| 22 | { | ||
| 23 | this.ExecutableName = executableName; | ||
| 24 | this.Section = section; | ||
| 25 | this.BundleSymbol = bundleSymbol; | ||
| 26 | this.Chain = chainSymbol; | ||
| 27 | this.Containers = containers; | ||
| 28 | this.OrderedPackages = orderedPackages; | ||
| 29 | this.RollbackBoundaries = boundaries; | ||
| 30 | this.UXContainerPayloads = uxPayloads; | ||
| 31 | this.Payloads = allPayloadsById; | ||
| 32 | this.PackagesPayloads = packagesPayloads; | ||
| 33 | this.OrderedSearches = orderedSearches; | ||
| 34 | this.IntermediateFolder = intermediateFolder; | ||
| 35 | } | ||
| 36 | |||
| 37 | public string OutputPath { get; private set; } | ||
| 38 | |||
| 39 | private string ExecutableName { get; } | ||
| 40 | |||
| 41 | private IntermediateSection Section { get; } | ||
| 42 | |||
| 43 | private WixBundleSymbol BundleSymbol { get; } | ||
| 44 | |||
| 45 | private WixChainSymbol Chain { get; } | ||
| 46 | |||
| 47 | private IEnumerable<WixBundleRollbackBoundarySymbol> RollbackBoundaries { get; } | ||
| 48 | |||
| 49 | private IEnumerable<PackageFacade> OrderedPackages { get; } | ||
| 50 | |||
| 51 | private IEnumerable<ISearchFacade> OrderedSearches { get; } | ||
| 52 | |||
| 53 | private Dictionary<string, WixBundlePayloadSymbol> Payloads { get; } | ||
| 54 | |||
| 55 | private Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> PackagesPayloads { get; } | ||
| 56 | |||
| 57 | private IEnumerable<WixBundleContainerSymbol> Containers { get; } | ||
| 58 | |||
| 59 | private IEnumerable<WixBundlePayloadSymbol> UXContainerPayloads { get; } | ||
| 60 | |||
| 61 | private string IntermediateFolder { get; } | ||
| 62 | |||
| 63 | public void Execute() | ||
| 64 | { | ||
| 65 | this.OutputPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); | ||
| 66 | |||
| 67 | using (var writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) | ||
| 68 | { | ||
| 69 | writer.WriteStartDocument(); | ||
| 70 | |||
| 71 | writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); | ||
| 72 | |||
| 73 | // Write the condition, if there is one | ||
| 74 | if (null != this.BundleSymbol.Condition) | ||
| 75 | { | ||
| 76 | writer.WriteElementString("Condition", this.BundleSymbol.Condition); | ||
| 77 | } | ||
| 78 | |||
| 79 | // Write the log element if default logging wasn't disabled. | ||
| 80 | if (!String.IsNullOrEmpty(this.BundleSymbol.LogPrefix)) | ||
| 81 | { | ||
| 82 | writer.WriteStartElement("Log"); | ||
| 83 | if (!String.IsNullOrEmpty(this.BundleSymbol.LogPathVariable)) | ||
| 84 | { | ||
| 85 | writer.WriteAttributeString("PathVariable", this.BundleSymbol.LogPathVariable); | ||
| 86 | } | ||
| 87 | writer.WriteAttributeString("Prefix", this.BundleSymbol.LogPrefix); | ||
| 88 | writer.WriteAttributeString("Extension", this.BundleSymbol.LogExtension); | ||
| 89 | writer.WriteEndElement(); | ||
| 90 | } | ||
| 91 | |||
| 92 | |||
| 93 | // Get update if specified. | ||
| 94 | var updateSymbol = this.Section.Symbols.OfType<WixBundleUpdateSymbol>().FirstOrDefault(); | ||
| 95 | |||
| 96 | if (null != updateSymbol) | ||
| 97 | { | ||
| 98 | writer.WriteStartElement("Update"); | ||
| 99 | writer.WriteAttributeString("Location", updateSymbol.Location); | ||
| 100 | writer.WriteEndElement(); // </Update> | ||
| 101 | } | ||
| 102 | |||
| 103 | // Write the RelatedBundle elements | ||
| 104 | |||
| 105 | // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates | ||
| 106 | // enumeration in the index row list is not used). | ||
| 107 | var relatedBundles = this.Section.Symbols.OfType<WixRelatedBundleSymbol>(); | ||
| 108 | var distinctRelatedBundles = new HashSet<string>(); | ||
| 109 | |||
| 110 | foreach (var relatedBundle in relatedBundles) | ||
| 111 | { | ||
| 112 | if (distinctRelatedBundles.Add(relatedBundle.BundleId)) | ||
| 113 | { | ||
| 114 | writer.WriteStartElement("RelatedBundle"); | ||
| 115 | writer.WriteAttributeString("Id", relatedBundle.BundleId); | ||
| 116 | writer.WriteAttributeString("Action", relatedBundle.Action.ToString()); | ||
| 117 | writer.WriteEndElement(); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | // Write the variables | ||
| 122 | var variables = this.Section.Symbols.OfType<WixBundleVariableSymbol>(); | ||
| 123 | |||
| 124 | foreach (var variable in variables) | ||
| 125 | { | ||
| 126 | writer.WriteStartElement("Variable"); | ||
| 127 | writer.WriteAttributeString("Id", variable.Id.Id); | ||
| 128 | if (variable.Type != WixBundleVariableType.Unknown) | ||
| 129 | { | ||
| 130 | writer.WriteAttributeString("Value", variable.Value); | ||
| 131 | |||
| 132 | switch (variable.Type) | ||
| 133 | { | ||
| 134 | case WixBundleVariableType.Formatted: | ||
| 135 | writer.WriteAttributeString("Type", "formatted"); | ||
| 136 | break; | ||
| 137 | case WixBundleVariableType.Numeric: | ||
| 138 | writer.WriteAttributeString("Type", "numeric"); | ||
| 139 | break; | ||
| 140 | case WixBundleVariableType.String: | ||
| 141 | writer.WriteAttributeString("Type", "string"); | ||
| 142 | break; | ||
| 143 | case WixBundleVariableType.Version: | ||
| 144 | writer.WriteAttributeString("Type", "version"); | ||
| 145 | break; | ||
| 146 | } | ||
| 147 | } | ||
| 148 | writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); | ||
| 149 | writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); | ||
| 150 | writer.WriteEndElement(); | ||
| 151 | } | ||
| 152 | |||
| 153 | // Write the searches | ||
| 154 | foreach (var searchinfo in this.OrderedSearches) | ||
| 155 | { | ||
| 156 | searchinfo.WriteXml(writer); | ||
| 157 | } | ||
| 158 | |||
| 159 | // write the UX element | ||
| 160 | writer.WriteStartElement("UX"); | ||
| 161 | if (!String.IsNullOrEmpty(this.BundleSymbol.SplashScreenSourceFile)) | ||
| 162 | { | ||
| 163 | writer.WriteAttributeString("SplashScreen", "yes"); | ||
| 164 | } | ||
| 165 | |||
| 166 | // write the UX allPayloads... | ||
| 167 | foreach (var payload in this.UXContainerPayloads) | ||
| 168 | { | ||
| 169 | this.WriteBurnManifestUXPayload(writer, payload); | ||
| 170 | } | ||
| 171 | |||
| 172 | writer.WriteEndElement(); // </UX> | ||
| 173 | |||
| 174 | foreach (var container in this.Containers) | ||
| 175 | { | ||
| 176 | if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) | ||
| 177 | { | ||
| 178 | writer.WriteStartElement("Container"); | ||
| 179 | this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); | ||
| 180 | writer.WriteEndElement(); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | foreach (var payload in this.Payloads.Values.Where(p => p.ContainerRef != BurnConstants.BurnUXContainerName)) | ||
| 185 | { | ||
| 186 | this.WriteBurnManifestPayload(writer, payload); | ||
| 187 | } | ||
| 188 | |||
| 189 | foreach (var rollbackBoundary in this.RollbackBoundaries) | ||
| 190 | { | ||
| 191 | writer.WriteStartElement("RollbackBoundary"); | ||
| 192 | writer.WriteAttributeString("Id", rollbackBoundary.Id.Id); | ||
| 193 | writer.WriteAttributeString("Vital", rollbackBoundary.Vital == false ? "no" : "yes"); | ||
| 194 | writer.WriteAttributeString("Transaction", rollbackBoundary.Transaction == true ? "yes" : "no"); | ||
| 195 | writer.WriteEndElement(); | ||
| 196 | } | ||
| 197 | |||
| 198 | // Write the registration information... | ||
| 199 | writer.WriteStartElement("Registration"); | ||
| 200 | |||
| 201 | writer.WriteAttributeString("Id", this.BundleSymbol.BundleId); | ||
| 202 | writer.WriteAttributeString("ExecutableName", this.ExecutableName); | ||
| 203 | writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); | ||
| 204 | writer.WriteAttributeString("Tag", this.BundleSymbol.Tag); | ||
| 205 | writer.WriteAttributeString("Version", this.BundleSymbol.Version); | ||
| 206 | writer.WriteAttributeString("ProviderKey", this.BundleSymbol.ProviderKey); | ||
| 207 | |||
| 208 | writer.WriteStartElement("Arp"); | ||
| 209 | writer.WriteAttributeString("Register", (this.BundleSymbol.DisableModify || this.BundleSymbol.SingleChangeUninstallButton) && this.BundleSymbol.DisableRemove ? "no" : "yes"); // do not register if disabled modify and remove. | ||
| 210 | writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); | ||
| 211 | writer.WriteAttributeString("DisplayVersion", this.BundleSymbol.Version); | ||
| 212 | |||
| 213 | if (!String.IsNullOrEmpty(this.BundleSymbol.Manufacturer)) | ||
| 214 | { | ||
| 215 | writer.WriteAttributeString("Publisher", this.BundleSymbol.Manufacturer); | ||
| 216 | } | ||
| 217 | |||
| 218 | if (!String.IsNullOrEmpty(this.BundleSymbol.HelpUrl)) | ||
| 219 | { | ||
| 220 | writer.WriteAttributeString("HelpLink", this.BundleSymbol.HelpUrl); | ||
| 221 | } | ||
| 222 | |||
| 223 | if (!String.IsNullOrEmpty(this.BundleSymbol.HelpTelephone)) | ||
| 224 | { | ||
| 225 | writer.WriteAttributeString("HelpTelephone", this.BundleSymbol.HelpTelephone); | ||
| 226 | } | ||
| 227 | |||
| 228 | if (!String.IsNullOrEmpty(this.BundleSymbol.AboutUrl)) | ||
| 229 | { | ||
| 230 | writer.WriteAttributeString("AboutUrl", this.BundleSymbol.AboutUrl); | ||
| 231 | } | ||
| 232 | |||
| 233 | if (!String.IsNullOrEmpty(this.BundleSymbol.UpdateUrl)) | ||
| 234 | { | ||
| 235 | writer.WriteAttributeString("UpdateUrl", this.BundleSymbol.UpdateUrl); | ||
| 236 | } | ||
| 237 | |||
| 238 | if (!String.IsNullOrEmpty(this.BundleSymbol.ParentName)) | ||
| 239 | { | ||
| 240 | writer.WriteAttributeString("ParentDisplayName", this.BundleSymbol.ParentName); | ||
| 241 | } | ||
| 242 | |||
| 243 | if (this.BundleSymbol.DisableModify) | ||
| 244 | { | ||
| 245 | writer.WriteAttributeString("DisableModify", "yes"); | ||
| 246 | } | ||
| 247 | |||
| 248 | if (this.BundleSymbol.DisableRemove) | ||
| 249 | { | ||
| 250 | writer.WriteAttributeString("DisableRemove", "yes"); | ||
| 251 | } | ||
| 252 | |||
| 253 | if (this.BundleSymbol.SingleChangeUninstallButton) | ||
| 254 | { | ||
| 255 | writer.WriteAttributeString("DisableModify", "button"); | ||
| 256 | } | ||
| 257 | writer.WriteEndElement(); // </Arp> | ||
| 258 | |||
| 259 | // Get update registration if specified. | ||
| 260 | var updateRegistrationInfo = this.Section.Symbols.OfType<WixUpdateRegistrationSymbol>().FirstOrDefault(); | ||
| 261 | |||
| 262 | if (null != updateRegistrationInfo) | ||
| 263 | { | ||
| 264 | writer.WriteStartElement("Update"); // <Update> | ||
| 265 | writer.WriteAttributeString("Manufacturer", updateRegistrationInfo.Manufacturer); | ||
| 266 | |||
| 267 | if (!String.IsNullOrEmpty(updateRegistrationInfo.Department)) | ||
| 268 | { | ||
| 269 | writer.WriteAttributeString("Department", updateRegistrationInfo.Department); | ||
| 270 | } | ||
| 271 | |||
| 272 | if (!String.IsNullOrEmpty(updateRegistrationInfo.ProductFamily)) | ||
| 273 | { | ||
| 274 | writer.WriteAttributeString("ProductFamily", updateRegistrationInfo.ProductFamily); | ||
| 275 | } | ||
| 276 | |||
| 277 | writer.WriteAttributeString("Name", updateRegistrationInfo.Name); | ||
| 278 | writer.WriteAttributeString("Classification", updateRegistrationInfo.Classification); | ||
| 279 | writer.WriteEndElement(); // </Update> | ||
| 280 | } | ||
| 281 | |||
| 282 | foreach (var bundleTagSymbol in this.Section.Symbols.OfType<WixBundleTagSymbol>()) | ||
| 283 | { | ||
| 284 | writer.WriteStartElement("SoftwareTag"); | ||
| 285 | writer.WriteAttributeString("Filename", bundleTagSymbol.Filename); | ||
| 286 | writer.WriteAttributeString("Regid", bundleTagSymbol.Regid); | ||
| 287 | writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath); | ||
| 288 | writer.WriteCData(bundleTagSymbol.Xml); | ||
| 289 | writer.WriteEndElement(); | ||
| 290 | } | ||
| 291 | |||
| 292 | writer.WriteEndElement(); // </Register> | ||
| 293 | |||
| 294 | // write the Chain... | ||
| 295 | writer.WriteStartElement("Chain"); | ||
| 296 | if (this.Chain.DisableRollback) | ||
| 297 | { | ||
| 298 | writer.WriteAttributeString("DisableRollback", "yes"); | ||
| 299 | } | ||
| 300 | |||
| 301 | if (this.Chain.DisableSystemRestore) | ||
| 302 | { | ||
| 303 | writer.WriteAttributeString("DisableSystemRestore", "yes"); | ||
| 304 | } | ||
| 305 | |||
| 306 | if (this.Chain.ParallelCache) | ||
| 307 | { | ||
| 308 | writer.WriteAttributeString("ParallelCache", "yes"); | ||
| 309 | } | ||
| 310 | |||
| 311 | // Index a few tables by package. | ||
| 312 | var targetCodesByPatch = this.Section.Symbols.OfType<WixBundlePatchTargetCodeSymbol>().ToLookup(r => r.PackageRef); | ||
| 313 | var msiFeaturesByPackage = this.Section.Symbols.OfType<WixBundleMsiFeatureSymbol>().ToLookup(r => r.PackageRef); | ||
| 314 | var msiPropertiesByPackage = this.Section.Symbols.OfType<WixBundleMsiPropertySymbol>().ToLookup(r => r.PackageRef); | ||
| 315 | var relatedPackagesByPackage = this.Section.Symbols.OfType<WixBundleRelatedPackageSymbol>().ToLookup(r => r.PackageRef); | ||
| 316 | var slipstreamMspsByPackage = this.Section.Symbols.OfType<WixBundleSlipstreamMspSymbol>().ToLookup(r => r.TargetPackageRef); | ||
| 317 | var exitCodesByPackage = this.Section.Symbols.OfType<WixBundlePackageExitCodeSymbol>().ToLookup(r => r.ChainPackageId); | ||
| 318 | var commandLinesByPackage = this.Section.Symbols.OfType<WixBundlePackageCommandLineSymbol>().ToLookup(r => r.WixBundlePackageRef); | ||
| 319 | |||
| 320 | var dependenciesByPackage = this.Section.Symbols.OfType<WixDependencyProviderSymbol>().ToLookup(p => p.ParentRef); | ||
| 321 | |||
| 322 | |||
| 323 | // Build up the list of target codes from all the MSPs in the chain. | ||
| 324 | var targetCodes = new List<WixBundlePatchTargetCodeSymbol>(); | ||
| 325 | |||
| 326 | foreach (var package in this.OrderedPackages) | ||
| 327 | { | ||
| 328 | writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.PackageSymbol.Type)); | ||
| 329 | |||
| 330 | writer.WriteAttributeString("Id", package.PackageId); | ||
| 331 | |||
| 332 | switch (package.PackageSymbol.Cache) | ||
| 333 | { | ||
| 334 | case YesNoAlwaysType.No: | ||
| 335 | writer.WriteAttributeString("Cache", "remove"); | ||
| 336 | break; | ||
| 337 | case YesNoAlwaysType.Yes: | ||
| 338 | writer.WriteAttributeString("Cache", "keep"); | ||
| 339 | break; | ||
| 340 | case YesNoAlwaysType.Always: | ||
| 341 | writer.WriteAttributeString("Cache", "force"); | ||
| 342 | break; | ||
| 343 | } | ||
| 344 | |||
| 345 | writer.WriteAttributeString("CacheId", package.PackageSymbol.CacheId); | ||
| 346 | writer.WriteAttributeString("InstallSize", Convert.ToString(package.PackageSymbol.InstallSize)); | ||
| 347 | writer.WriteAttributeString("Size", Convert.ToString(package.PackageSymbol.Size)); | ||
| 348 | writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.PackageSymbol.PerMachine ? "yes" : "no"); | ||
| 349 | writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); | ||
| 350 | writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == false ? "no" : "yes"); | ||
| 351 | |||
| 352 | if (null != package.PackageSymbol.RollbackBoundaryRef) | ||
| 353 | { | ||
| 354 | writer.WriteAttributeString("RollbackBoundaryForward", package.PackageSymbol.RollbackBoundaryRef); | ||
| 355 | } | ||
| 356 | |||
| 357 | if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackBoundaryBackwardRef)) | ||
| 358 | { | ||
| 359 | writer.WriteAttributeString("RollbackBoundaryBackward", package.PackageSymbol.RollbackBoundaryBackwardRef); | ||
| 360 | } | ||
| 361 | |||
| 362 | if (!String.IsNullOrEmpty(package.PackageSymbol.LogPathVariable)) | ||
| 363 | { | ||
| 364 | writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); | ||
| 365 | } | ||
| 366 | |||
| 367 | if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackLogPathVariable)) | ||
| 368 | { | ||
| 369 | writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); | ||
| 370 | } | ||
| 371 | |||
| 372 | if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) | ||
| 373 | { | ||
| 374 | writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); | ||
| 375 | } | ||
| 376 | |||
| 377 | if (package.SpecificPackageSymbol is WixBundleExePackageSymbol exePackage) // EXE | ||
| 378 | { | ||
| 379 | writer.WriteAttributeString("DetectCondition", exePackage.DetectCondition); | ||
| 380 | writer.WriteAttributeString("InstallArguments", exePackage.InstallCommand); | ||
| 381 | writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand); | ||
| 382 | writer.WriteAttributeString("RepairArguments", exePackage.RepairCommand); | ||
| 383 | writer.WriteAttributeString("Repairable", exePackage.Repairable ? "yes" : "no"); | ||
| 384 | if (!String.IsNullOrEmpty(exePackage.ExeProtocol)) | ||
| 385 | { | ||
| 386 | writer.WriteAttributeString("Protocol", exePackage.ExeProtocol); | ||
| 387 | } | ||
| 388 | } | ||
| 389 | else if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) // MSI | ||
| 390 | { | ||
| 391 | writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); | ||
| 392 | writer.WriteAttributeString("Language", msiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); | ||
| 393 | writer.WriteAttributeString("Version", msiPackage.ProductVersion); | ||
| 394 | if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) | ||
| 395 | { | ||
| 396 | writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); | ||
| 397 | } | ||
| 398 | } | ||
| 399 | else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) // MSP | ||
| 400 | { | ||
| 401 | writer.WriteAttributeString("PatchCode", mspPackage.PatchCode); | ||
| 402 | writer.WriteAttributeString("PatchXml", mspPackage.PatchXml); | ||
| 403 | |||
| 404 | // If there is still a chance that all of our patches will target a narrow set of | ||
| 405 | // product codes, add the patch list to the overall list. | ||
| 406 | if (null != targetCodes) | ||
| 407 | { | ||
| 408 | if (!mspPackage.TargetUnspecified) | ||
| 409 | { | ||
| 410 | var patchTargetCodes = targetCodesByPatch[mspPackage.Id.Id]; | ||
| 411 | |||
| 412 | targetCodes.AddRange(patchTargetCodes); | ||
| 413 | } | ||
| 414 | else // we have a patch that targets the world, so throw the whole list away. | ||
| 415 | { | ||
| 416 | targetCodes = null; | ||
| 417 | } | ||
| 418 | } | ||
| 419 | } | ||
| 420 | else if (package.SpecificPackageSymbol is WixBundleMsuPackageSymbol msuPackage) // MSU | ||
| 421 | { | ||
| 422 | writer.WriteAttributeString("DetectCondition", msuPackage.DetectCondition); | ||
| 423 | writer.WriteAttributeString("KB", msuPackage.MsuKB); | ||
| 424 | } | ||
| 425 | |||
| 426 | var packageMsiFeatures = msiFeaturesByPackage[package.PackageId]; | ||
| 427 | |||
| 428 | foreach (var feature in packageMsiFeatures) | ||
| 429 | { | ||
| 430 | writer.WriteStartElement("MsiFeature"); | ||
| 431 | writer.WriteAttributeString("Id", feature.Name); | ||
| 432 | writer.WriteEndElement(); | ||
| 433 | } | ||
| 434 | |||
| 435 | var packageMsiProperties = msiPropertiesByPackage[package.PackageId]; | ||
| 436 | |||
| 437 | foreach (var msiProperty in packageMsiProperties) | ||
| 438 | { | ||
| 439 | writer.WriteStartElement("MsiProperty"); | ||
| 440 | writer.WriteAttributeString("Id", msiProperty.Name); | ||
| 441 | writer.WriteAttributeString("Value", msiProperty.Value); | ||
| 442 | if (!String.IsNullOrEmpty(msiProperty.Condition)) | ||
| 443 | { | ||
| 444 | writer.WriteAttributeString("Condition", msiProperty.Condition); | ||
| 445 | } | ||
| 446 | writer.WriteEndElement(); | ||
| 447 | } | ||
| 448 | |||
| 449 | var packageSlipstreamMsps = slipstreamMspsByPackage[package.PackageId]; | ||
| 450 | |||
| 451 | foreach (var slipstreamMsp in packageSlipstreamMsps) | ||
| 452 | { | ||
| 453 | writer.WriteStartElement("SlipstreamMsp"); | ||
| 454 | writer.WriteAttributeString("Id", slipstreamMsp.MspPackageRef); | ||
| 455 | writer.WriteEndElement(); | ||
| 456 | } | ||
| 457 | |||
| 458 | var packageExitCodes = exitCodesByPackage[package.PackageId]; | ||
| 459 | |||
| 460 | foreach (var exitCode in packageExitCodes) | ||
| 461 | { | ||
| 462 | writer.WriteStartElement("ExitCode"); | ||
| 463 | |||
| 464 | if (exitCode.Code.HasValue) | ||
| 465 | { | ||
| 466 | writer.WriteAttributeString("Code", unchecked((uint)exitCode.Code).ToString(CultureInfo.InvariantCulture)); | ||
| 467 | } | ||
| 468 | else | ||
| 469 | { | ||
| 470 | writer.WriteAttributeString("Code", "*"); | ||
| 471 | } | ||
| 472 | |||
| 473 | writer.WriteAttributeString("Type", ((int)exitCode.Behavior).ToString(CultureInfo.InvariantCulture)); | ||
| 474 | writer.WriteEndElement(); | ||
| 475 | } | ||
| 476 | |||
| 477 | var packageCommandLines = commandLinesByPackage[package.PackageId]; | ||
| 478 | |||
| 479 | foreach (var commandLine in packageCommandLines) | ||
| 480 | { | ||
| 481 | writer.WriteStartElement("CommandLine"); | ||
| 482 | writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument); | ||
| 483 | writer.WriteAttributeString("UninstallArgument", commandLine.UninstallArgument); | ||
| 484 | writer.WriteAttributeString("RepairArgument", commandLine.RepairArgument); | ||
| 485 | writer.WriteAttributeString("Condition", commandLine.Condition); | ||
| 486 | writer.WriteEndElement(); | ||
| 487 | } | ||
| 488 | |||
| 489 | // Output the dependency information. | ||
| 490 | var dependencies = dependenciesByPackage[package.PackageId]; | ||
| 491 | |||
| 492 | foreach (var dependency in dependencies) | ||
| 493 | { | ||
| 494 | writer.WriteStartElement("Provides"); | ||
| 495 | writer.WriteAttributeString("Key", dependency.ProviderKey); | ||
| 496 | |||
| 497 | if (!String.IsNullOrEmpty(dependency.Version)) | ||
| 498 | { | ||
| 499 | writer.WriteAttributeString("Version", dependency.Version); | ||
| 500 | } | ||
| 501 | |||
| 502 | if (!String.IsNullOrEmpty(dependency.DisplayName)) | ||
| 503 | { | ||
| 504 | writer.WriteAttributeString("DisplayName", dependency.DisplayName); | ||
| 505 | } | ||
| 506 | |||
| 507 | if (dependency.Imported) | ||
| 508 | { | ||
| 509 | // The package dependency was explicitly authored into the manifest. | ||
| 510 | writer.WriteAttributeString("Imported", "yes"); | ||
| 511 | } | ||
| 512 | |||
| 513 | writer.WriteEndElement(); | ||
| 514 | } | ||
| 515 | |||
| 516 | var packageRelatedPackages = relatedPackagesByPackage[package.PackageId]; | ||
| 517 | |||
| 518 | foreach (var related in packageRelatedPackages) | ||
| 519 | { | ||
| 520 | writer.WriteStartElement("RelatedPackage"); | ||
| 521 | writer.WriteAttributeString("Id", related.RelatedId); | ||
| 522 | if (!String.IsNullOrEmpty(related.MinVersion)) | ||
| 523 | { | ||
| 524 | writer.WriteAttributeString("MinVersion", related.MinVersion); | ||
| 525 | writer.WriteAttributeString("MinInclusive", related.MinInclusive ? "yes" : "no"); | ||
| 526 | } | ||
| 527 | if (!String.IsNullOrEmpty(related.MaxVersion)) | ||
| 528 | { | ||
| 529 | writer.WriteAttributeString("MaxVersion", related.MaxVersion); | ||
| 530 | writer.WriteAttributeString("MaxInclusive", related.MaxInclusive ? "yes" : "no"); | ||
| 531 | } | ||
| 532 | writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no"); | ||
| 533 | |||
| 534 | var relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); | ||
| 535 | |||
| 536 | if (0 < relatedLanguages.Length) | ||
| 537 | { | ||
| 538 | writer.WriteAttributeString("LangInclusive", related.LangInclusive ? "yes" : "no"); | ||
| 539 | foreach (string language in relatedLanguages) | ||
| 540 | { | ||
| 541 | writer.WriteStartElement("Language"); | ||
| 542 | writer.WriteAttributeString("Id", language); | ||
| 543 | writer.WriteEndElement(); | ||
| 544 | } | ||
| 545 | } | ||
| 546 | writer.WriteEndElement(); | ||
| 547 | } | ||
| 548 | |||
| 549 | // Write any contained Payloads with the PackagePayload being first | ||
| 550 | var packagePayloadId = package.PackageSymbol.PayloadRef; | ||
| 551 | writer.WriteStartElement("PayloadRef"); | ||
| 552 | writer.WriteAttributeString("Id", packagePayloadId); | ||
| 553 | writer.WriteEndElement(); | ||
| 554 | |||
| 555 | var packagePayloads = this.PackagesPayloads[package.PackageId]; | ||
| 556 | |||
| 557 | foreach (var payload in packagePayloads.Values) | ||
| 558 | { | ||
| 559 | if (payload.Id.Id != packagePayloadId) | ||
| 560 | { | ||
| 561 | writer.WriteStartElement("PayloadRef"); | ||
| 562 | writer.WriteAttributeString("Id", payload.Id.Id); | ||
| 563 | writer.WriteEndElement(); | ||
| 564 | } | ||
| 565 | } | ||
| 566 | |||
| 567 | writer.WriteEndElement(); // </XxxPackage> | ||
| 568 | } | ||
| 569 | writer.WriteEndElement(); // </Chain> | ||
| 570 | |||
| 571 | if (null != targetCodes) | ||
| 572 | { | ||
| 573 | foreach (var targetCode in targetCodes) | ||
| 574 | { | ||
| 575 | writer.WriteStartElement("PatchTargetCode"); | ||
| 576 | writer.WriteAttributeString("TargetCode", targetCode.TargetCode); | ||
| 577 | writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no"); | ||
| 578 | writer.WriteEndElement(); | ||
| 579 | } | ||
| 580 | } | ||
| 581 | |||
| 582 | // Write the ApprovedExeForElevation elements. | ||
| 583 | var approvedExesForElevation = this.Section.Symbols.OfType<WixApprovedExeForElevationSymbol>(); | ||
| 584 | |||
| 585 | foreach (var approvedExeForElevation in approvedExesForElevation) | ||
| 586 | { | ||
| 587 | writer.WriteStartElement("ApprovedExeForElevation"); | ||
| 588 | writer.WriteAttributeString("Id", approvedExeForElevation.Id.Id); | ||
| 589 | writer.WriteAttributeString("Key", approvedExeForElevation.Key); | ||
| 590 | |||
| 591 | if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName)) | ||
| 592 | { | ||
| 593 | writer.WriteAttributeString("ValueName", approvedExeForElevation.ValueName); | ||
| 594 | } | ||
| 595 | |||
| 596 | if (approvedExeForElevation.Win64) | ||
| 597 | { | ||
| 598 | writer.WriteAttributeString("Win64", "yes"); | ||
| 599 | } | ||
| 600 | |||
| 601 | writer.WriteEndElement(); | ||
| 602 | } | ||
| 603 | |||
| 604 | // Write the BundleExtension elements. | ||
| 605 | var bundleExtensions = this.Section.Symbols.OfType<WixBundleExtensionSymbol>(); | ||
| 606 | |||
| 607 | foreach (var bundleExtension in bundleExtensions) | ||
| 608 | { | ||
| 609 | writer.WriteStartElement("BundleExtension"); | ||
| 610 | writer.WriteAttributeString("Id", bundleExtension.Id.Id); | ||
| 611 | writer.WriteAttributeString("EntryPayloadId", bundleExtension.PayloadRef); | ||
| 612 | |||
| 613 | writer.WriteEndElement(); | ||
| 614 | } | ||
| 615 | |||
| 616 | writer.WriteEndDocument(); // </BurnManifest> | ||
| 617 | } | ||
| 618 | } | ||
| 619 | |||
| 620 | private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerSymbol container) | ||
| 621 | { | ||
| 622 | writer.WriteAttributeString("Id", container.Id.Id); | ||
| 623 | writer.WriteAttributeString("FileSize", container.Size.Value.ToString(CultureInfo.InvariantCulture)); | ||
| 624 | writer.WriteAttributeString("Hash", container.Hash); | ||
| 625 | |||
| 626 | if (ContainerType.Detached == container.Type) | ||
| 627 | { | ||
| 628 | if (!String.IsNullOrEmpty(container.DownloadUrl)) | ||
| 629 | { | ||
| 630 | writer.WriteAttributeString("DownloadUrl", container.DownloadUrl); | ||
| 631 | } | ||
| 632 | |||
| 633 | writer.WriteAttributeString("FilePath", container.Name); | ||
| 634 | } | ||
| 635 | else if (ContainerType.Attached == container.Type) | ||
| 636 | { | ||
| 637 | writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. | ||
| 638 | writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.Value.ToString(CultureInfo.InvariantCulture)); | ||
| 639 | writer.WriteAttributeString("Attached", "yes"); | ||
| 640 | writer.WriteAttributeString("Primary", "yes"); | ||
| 641 | } | ||
| 642 | } | ||
| 643 | |||
| 644 | private void WriteBurnManifestPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) | ||
| 645 | { | ||
| 646 | writer.WriteStartElement("Payload"); | ||
| 647 | |||
| 648 | writer.WriteAttributeString("Id", payload.Id.Id); | ||
| 649 | writer.WriteAttributeString("FilePath", payload.Name); | ||
| 650 | writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); | ||
| 651 | writer.WriteAttributeString("Hash", payload.Hash); | ||
| 652 | |||
| 653 | if (payload.LayoutOnly) | ||
| 654 | { | ||
| 655 | writer.WriteAttributeString("LayoutOnly", "yes"); | ||
| 656 | } | ||
| 657 | |||
| 658 | if (!String.IsNullOrEmpty(payload.DownloadUrl)) | ||
| 659 | { | ||
| 660 | writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); | ||
| 661 | } | ||
| 662 | |||
| 663 | switch (payload.Packaging) | ||
| 664 | { | ||
| 665 | case PackagingType.Embedded: // this means it's in a container. | ||
| 666 | Debug.Assert(BurnConstants.BurnUXContainerName != payload.ContainerRef); | ||
| 667 | |||
| 668 | writer.WriteAttributeString("Packaging", "embedded"); | ||
| 669 | writer.WriteAttributeString("SourcePath", payload.EmbeddedId); | ||
| 670 | writer.WriteAttributeString("Container", payload.ContainerRef); | ||
| 671 | break; | ||
| 672 | |||
| 673 | case PackagingType.External: | ||
| 674 | writer.WriteAttributeString("Packaging", "external"); | ||
| 675 | writer.WriteAttributeString("SourcePath", payload.Name); | ||
| 676 | break; | ||
| 677 | } | ||
| 678 | |||
| 679 | writer.WriteEndElement(); | ||
| 680 | } | ||
| 681 | |||
| 682 | private void WriteBurnManifestUXPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) | ||
| 683 | { | ||
| 684 | Debug.Assert(PackagingType.Embedded == payload.Packaging); | ||
| 685 | Debug.Assert(BurnConstants.BurnUXContainerName == payload.ContainerRef); | ||
| 686 | |||
| 687 | writer.WriteStartElement("Payload"); | ||
| 688 | |||
| 689 | // TODO: The engine should be updated to not require FileSize, Hash, or Packaging for UX payloads since the values are never used. | ||
| 690 | writer.WriteAttributeString("Id", payload.Id.Id); | ||
| 691 | writer.WriteAttributeString("FilePath", payload.Name); | ||
| 692 | writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); | ||
| 693 | writer.WriteAttributeString("Hash", payload.Hash); | ||
| 694 | writer.WriteAttributeString("Packaging", "embedded"); | ||
| 695 | writer.WriteAttributeString("SourcePath", payload.EmbeddedId); | ||
| 696 | |||
| 697 | writer.WriteEndElement(); | ||
| 698 | } | ||
| 699 | } | ||
| 700 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs new file mode 100644 index 00000000..87a63cc3 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Core.Native; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// Creates cabinet files. | ||
| 15 | /// </summary> | ||
| 16 | internal class CreateContainerCommand | ||
| 17 | { | ||
| 18 | public CreateContainerCommand(IEnumerable<WixBundlePayloadSymbol> payloads, string outputPath, CompressionLevel? compressionLevel) | ||
| 19 | { | ||
| 20 | this.Payloads = payloads; | ||
| 21 | this.OutputPath = outputPath; | ||
| 22 | this.CompressionLevel = compressionLevel; | ||
| 23 | } | ||
| 24 | |||
| 25 | public CreateContainerCommand(string manifestPath, IEnumerable<WixBundlePayloadSymbol> payloads, string outputPath, CompressionLevel? compressionLevel) | ||
| 26 | { | ||
| 27 | this.ManifestFile = manifestPath; | ||
| 28 | this.Payloads = payloads; | ||
| 29 | this.OutputPath = outputPath; | ||
| 30 | this.CompressionLevel = compressionLevel; | ||
| 31 | } | ||
| 32 | |||
| 33 | private CompressionLevel? CompressionLevel { get; } | ||
| 34 | |||
| 35 | private string ManifestFile { get; } | ||
| 36 | |||
| 37 | private string OutputPath { get; } | ||
| 38 | |||
| 39 | private IEnumerable<WixBundlePayloadSymbol> Payloads { get; } | ||
| 40 | |||
| 41 | public string Hash { get; private set; } | ||
| 42 | |||
| 43 | public long Size { get; private set; } | ||
| 44 | |||
| 45 | public void Execute() | ||
| 46 | { | ||
| 47 | var cabinetPath = Path.GetFullPath(this.OutputPath); | ||
| 48 | |||
| 49 | var files = new List<CabinetCompressFile>(); | ||
| 50 | |||
| 51 | // If a manifest was provided always add it as "payload 0" to the container. | ||
| 52 | if (!String.IsNullOrEmpty(this.ManifestFile)) | ||
| 53 | { | ||
| 54 | files.Add(new CabinetCompressFile(this.ManifestFile, "0")); | ||
| 55 | } | ||
| 56 | |||
| 57 | files.AddRange(this.Payloads.Select(p => new CabinetCompressFile(p.SourceFile.Path, p.EmbeddedId))); | ||
| 58 | |||
| 59 | var cab = new Cabinet(cabinetPath); | ||
| 60 | cab.Compress(files, this.CompressionLevel ?? Data.CompressionLevel.Medium); | ||
| 61 | |||
| 62 | // Now that the container is created, set the outputs of the command. | ||
| 63 | var fileInfo = new FileInfo(cabinetPath); | ||
| 64 | |||
| 65 | this.Hash = BundleHashAlgorithm.Hash(fileInfo); | ||
| 66 | |||
| 67 | this.Size = fileInfo.Length; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs new file mode 100644 index 00000000..f020ed84 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs | |||
| @@ -0,0 +1,151 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Burn; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Extensibility.Data; | ||
| 14 | using WixToolset.Extensibility.Services; | ||
| 15 | |||
| 16 | internal class CreateNonUXContainers | ||
| 17 | { | ||
| 18 | public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable<WixBundleContainerSymbol> containerSymbols, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) | ||
| 19 | { | ||
| 20 | this.BackendHelper = backendHelper; | ||
| 21 | this.Messaging = messaging; | ||
| 22 | this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; | ||
| 23 | this.Containers = containerSymbols; | ||
| 24 | this.PayloadSymbols = payloadSymbols; | ||
| 25 | this.IntermediateFolder = intermediateFolder; | ||
| 26 | this.LayoutFolder = layoutFolder; | ||
| 27 | this.DefaultCompressionLevel = defaultCompressionLevel; | ||
| 28 | } | ||
| 29 | |||
| 30 | public IEnumerable<IFileTransfer> FileTransfers { get; private set; } | ||
| 31 | |||
| 32 | public IEnumerable<ITrackedFile> TrackedFiles { get; private set; } | ||
| 33 | |||
| 34 | public WixBundleContainerSymbol UXContainer { get; set; } | ||
| 35 | |||
| 36 | public IEnumerable<WixBundlePayloadSymbol> UXContainerPayloads { get; private set; } | ||
| 37 | |||
| 38 | private IEnumerable<WixBundleContainerSymbol> Containers { get; } | ||
| 39 | |||
| 40 | private IBackendHelper BackendHelper { get; } | ||
| 41 | |||
| 42 | private IMessaging Messaging { get; } | ||
| 43 | |||
| 44 | private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } | ||
| 45 | |||
| 46 | private Dictionary<string, WixBundlePayloadSymbol> PayloadSymbols { get; } | ||
| 47 | |||
| 48 | private string IntermediateFolder { get; } | ||
| 49 | |||
| 50 | private string LayoutFolder { get; } | ||
| 51 | |||
| 52 | private CompressionLevel? DefaultCompressionLevel { get; } | ||
| 53 | |||
| 54 | public void Execute() | ||
| 55 | { | ||
| 56 | var fileTransfers = new List<IFileTransfer>(); | ||
| 57 | var trackedFiles = new List<ITrackedFile>(); | ||
| 58 | var uxPayloadSymbols = new List<WixBundlePayloadSymbol>(); | ||
| 59 | |||
| 60 | var attachedContainerIndex = 1; // count starts at one because UX container is "0". | ||
| 61 | |||
| 62 | var payloadsByContainer = this.PayloadSymbols.Values.ToLookup(p => p.ContainerRef); | ||
| 63 | |||
| 64 | foreach (var container in this.Containers) | ||
| 65 | { | ||
| 66 | var containerId = container.Id.Id; | ||
| 67 | |||
| 68 | var containerPayloads = payloadsByContainer[containerId]; | ||
| 69 | |||
| 70 | if (!containerPayloads.Any()) | ||
| 71 | { | ||
| 72 | if (containerId != BurnConstants.BurnDefaultAttachedContainerName) | ||
| 73 | { | ||
| 74 | this.Messaging.Write(BurnBackendWarnings.EmptyContainer(container.SourceLineNumbers, containerId)); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | else if (BurnConstants.BurnUXContainerName == containerId) | ||
| 78 | { | ||
| 79 | this.UXContainer = container; | ||
| 80 | |||
| 81 | container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); | ||
| 82 | container.AttachedContainerIndex = 0; | ||
| 83 | |||
| 84 | // Gather the list of UX payloads but ensure the BootstrapperApplicationDll Payload is the first | ||
| 85 | // in the list since that is the Payload that Burn attempts to load. | ||
| 86 | var baPayloadId = this.BootstrapperApplicationDllSymbol.Id.Id; | ||
| 87 | |||
| 88 | foreach (var uxPayload in containerPayloads) | ||
| 89 | { | ||
| 90 | if (uxPayload.Id.Id == baPayloadId) | ||
| 91 | { | ||
| 92 | uxPayloadSymbols.Insert(0, uxPayload); | ||
| 93 | } | ||
| 94 | else | ||
| 95 | { | ||
| 96 | uxPayloadSymbols.Add(uxPayload); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
| 100 | else | ||
| 101 | { | ||
| 102 | container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); | ||
| 103 | |||
| 104 | // Add detached containers to the list of file transfers. | ||
| 105 | if (ContainerType.Detached == container.Type) | ||
| 106 | { | ||
| 107 | var transfer = this.BackendHelper.CreateFileTransfer(container.WorkingPath, Path.Combine(this.LayoutFolder, container.Name), true, container.SourceLineNumbers); | ||
| 108 | fileTransfers.Add(transfer); | ||
| 109 | } | ||
| 110 | else // update the attached container index. | ||
| 111 | { | ||
| 112 | Debug.Assert(ContainerType.Attached == container.Type); | ||
| 113 | |||
| 114 | container.AttachedContainerIndex = attachedContainerIndex; | ||
| 115 | ++attachedContainerIndex; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) | ||
| 121 | { | ||
| 122 | if (container.Type == ContainerType.Attached && attachedContainerIndex > 2 && container.Id.Id != BurnConstants.BurnDefaultAttachedContainerName) | ||
| 123 | { | ||
| 124 | this.Messaging.Write(BurnBackendErrors.MultipleAttachedContainersUnsupported(container.SourceLineNumbers, container.Id.Id)); | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | if (!this.Messaging.EncounteredError) | ||
| 129 | { | ||
| 130 | foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) | ||
| 131 | { | ||
| 132 | this.CreateContainer(container, payloadsByContainer[container.Id.Id]); | ||
| 133 | trackedFiles.Add(this.BackendHelper.TrackFile(container.WorkingPath, TrackedFileType.Temporary, container.SourceLineNumbers)); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | this.UXContainerPayloads = uxPayloadSymbols; | ||
| 138 | this.FileTransfers = fileTransfers; | ||
| 139 | this.TrackedFiles = trackedFiles; | ||
| 140 | } | ||
| 141 | |||
| 142 | private void CreateContainer(WixBundleContainerSymbol container, IEnumerable<WixBundlePayloadSymbol> containerPayloads) | ||
| 143 | { | ||
| 144 | var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); | ||
| 145 | command.Execute(); | ||
| 146 | |||
| 147 | container.Hash = command.Hash; | ||
| 148 | container.Size = command.Size; | ||
| 149 | } | ||
| 150 | } | ||
| 151 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs new file mode 100644 index 00000000..bfb6b918 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs | |||
| @@ -0,0 +1,137 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Burn; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class DetectPayloadCollisionsCommand | ||
| 14 | { | ||
| 15 | public DetectPayloadCollisionsCommand(IMessaging messaging, Dictionary<string, WixBundleContainerSymbol> containerSymbols, IEnumerable<PackageFacade> packages, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols, Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> packagePayloads) | ||
| 16 | { | ||
| 17 | this.Messaging = messaging; | ||
| 18 | this.Containers = containerSymbols; | ||
| 19 | this.Packages = packages; | ||
| 20 | this.PayloadSymbols = payloadSymbols; | ||
| 21 | this.PackagePayloads = packagePayloads; | ||
| 22 | } | ||
| 23 | |||
| 24 | private IMessaging Messaging { get; } | ||
| 25 | |||
| 26 | private Dictionary<string, WixBundleContainerSymbol> Containers { get; } | ||
| 27 | |||
| 28 | private IEnumerable<PackageFacade> Packages { get; } | ||
| 29 | |||
| 30 | private Dictionary<string, WixBundlePayloadSymbol> PayloadSymbols { get; } | ||
| 31 | |||
| 32 | private Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> PackagePayloads { get; } | ||
| 33 | |||
| 34 | public void Execute() | ||
| 35 | { | ||
| 36 | this.DetectAttachedContainerCollisions(); | ||
| 37 | this.DetectExternalCollisions(); | ||
| 38 | this.DetectPackageCacheCollisions(); | ||
| 39 | } | ||
| 40 | |||
| 41 | public void DetectAttachedContainerCollisions() | ||
| 42 | { | ||
| 43 | var attachedContainerPayloadsByNameByContainer = new Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>>(); | ||
| 44 | |||
| 45 | foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.Embedded)) | ||
| 46 | { | ||
| 47 | var containerId = payload.ContainerRef; | ||
| 48 | var container = this.Containers[containerId]; | ||
| 49 | if (container.Type == ContainerType.Attached) | ||
| 50 | { | ||
| 51 | if (!attachedContainerPayloadsByNameByContainer.TryGetValue(containerId, out var attachedContainerPayloadsByName)) | ||
| 52 | { | ||
| 53 | attachedContainerPayloadsByName = new Dictionary<string, WixBundlePayloadSymbol>(StringComparer.OrdinalIgnoreCase); | ||
| 54 | attachedContainerPayloadsByNameByContainer.Add(containerId, attachedContainerPayloadsByName); | ||
| 55 | } | ||
| 56 | |||
| 57 | if (!attachedContainerPayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) | ||
| 58 | { | ||
| 59 | attachedContainerPayloadsByName.Add(payload.Name, payload); | ||
| 60 | } | ||
| 61 | else | ||
| 62 | { | ||
| 63 | if (containerId == BurnConstants.BurnUXContainerName) | ||
| 64 | { | ||
| 65 | this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); | ||
| 66 | this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); | ||
| 67 | } | ||
| 68 | else | ||
| 69 | { | ||
| 70 | this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); | ||
| 71 | this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | public void DetectExternalCollisions() | ||
| 79 | { | ||
| 80 | var externalPayloadsByName = new Dictionary<string, IntermediateSymbol>(StringComparer.OrdinalIgnoreCase); | ||
| 81 | |||
| 82 | foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.External)) | ||
| 83 | { | ||
| 84 | if (!externalPayloadsByName.TryGetValue(payload.Name, out var collisionSymbol)) | ||
| 85 | { | ||
| 86 | externalPayloadsByName.Add(payload.Name, payload); | ||
| 87 | } | ||
| 88 | else | ||
| 89 | { | ||
| 90 | this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(payload.SourceLineNumbers, "Payload", payload.Id.Id, payload.Name)); | ||
| 91 | this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | foreach (var container in this.Containers.Values.Where(c => c.Type == ContainerType.Detached)) | ||
| 96 | { | ||
| 97 | if (!externalPayloadsByName.TryGetValue(container.Name, out var collisionSymbol)) | ||
| 98 | { | ||
| 99 | externalPayloadsByName.Add(container.Name, container); | ||
| 100 | } | ||
| 101 | else | ||
| 102 | { | ||
| 103 | this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(container.SourceLineNumbers, "Container", container.Id.Id, container.Name)); | ||
| 104 | this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | public void DetectPackageCacheCollisions() | ||
| 110 | { | ||
| 111 | var packageCachePayloadsByNameByCacheId = new Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>>(); | ||
| 112 | |||
| 113 | foreach (var packageFacade in this.Packages) | ||
| 114 | { | ||
| 115 | var packagePayloads = this.PackagePayloads[packageFacade.PackageId]; | ||
| 116 | if (!packageCachePayloadsByNameByCacheId.TryGetValue(packageFacade.PackageSymbol.CacheId, out var packageCachePayloadsByName)) | ||
| 117 | { | ||
| 118 | packageCachePayloadsByName = new Dictionary<string, WixBundlePayloadSymbol>(StringComparer.OrdinalIgnoreCase); | ||
| 119 | packageCachePayloadsByNameByCacheId.Add(packageFacade.PackageSymbol.CacheId, packageCachePayloadsByName); | ||
| 120 | } | ||
| 121 | |||
| 122 | foreach (var payload in packagePayloads.Values) | ||
| 123 | { | ||
| 124 | if (!packageCachePayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) | ||
| 125 | { | ||
| 126 | packageCachePayloadsByName.Add(payload.Name, payload); | ||
| 127 | } | ||
| 128 | else | ||
| 129 | { | ||
| 130 | this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name, packageFacade.PackageId)); | ||
| 131 | this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision2(collisionPayload.SourceLineNumbers)); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | } | ||
| 136 | } | ||
| 137 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs new file mode 100644 index 00000000..b8b256fd --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs | |||
| @@ -0,0 +1,181 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.Linq; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Symbols; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | internal class GetPackageFacadesCommand | ||
| 12 | { | ||
| 13 | public GetPackageFacadesCommand(IMessaging messaging, IEnumerable<WixBundlePackageSymbol> chainPackageSymbols, IntermediateSection section) | ||
| 14 | { | ||
| 15 | this.Messaging = messaging; | ||
| 16 | this.ChainPackageSymbols = chainPackageSymbols; | ||
| 17 | this.Section = section; | ||
| 18 | } | ||
| 19 | |||
| 20 | private IEnumerable<WixBundlePackageSymbol> ChainPackageSymbols { get; } | ||
| 21 | |||
| 22 | private IMessaging Messaging { get; } | ||
| 23 | |||
| 24 | private IntermediateSection Section { get; } | ||
| 25 | |||
| 26 | public IDictionary<string, PackageFacade> PackageFacades { get; private set; } | ||
| 27 | |||
| 28 | public void Execute() | ||
| 29 | { | ||
| 30 | var wixGroupPackagesGroupedById = this.Section.Symbols.OfType<WixGroupSymbol>().Where(g => g.ParentType == ComplexReferenceParentType.Package).ToLookup(g => g.ParentId); | ||
| 31 | var exePackages = this.Section.Symbols.OfType<WixBundleExePackageSymbol>().ToDictionary(t => t.Id.Id); | ||
| 32 | var msiPackages = this.Section.Symbols.OfType<WixBundleMsiPackageSymbol>().ToDictionary(t => t.Id.Id); | ||
| 33 | var mspPackages = this.Section.Symbols.OfType<WixBundleMspPackageSymbol>().ToDictionary(t => t.Id.Id); | ||
| 34 | var msuPackages = this.Section.Symbols.OfType<WixBundleMsuPackageSymbol>().ToDictionary(t => t.Id.Id); | ||
| 35 | var exePackagePayloads = this.Section.Symbols.OfType<WixBundleExePackagePayloadSymbol>().ToDictionary(t => t.Id.Id); | ||
| 36 | var msiPackagePayloads = this.Section.Symbols.OfType<WixBundleMsiPackagePayloadSymbol>().ToDictionary(t => t.Id.Id); | ||
| 37 | var mspPackagePayloads = this.Section.Symbols.OfType<WixBundleMspPackagePayloadSymbol>().ToDictionary(t => t.Id.Id); | ||
| 38 | var msuPackagePayloads = this.Section.Symbols.OfType<WixBundleMsuPackagePayloadSymbol>().ToDictionary(t => t.Id.Id); | ||
| 39 | |||
| 40 | var facades = new Dictionary<string, PackageFacade>(); | ||
| 41 | |||
| 42 | foreach (var package in this.ChainPackageSymbols) | ||
| 43 | { | ||
| 44 | var id = package.Id.Id; | ||
| 45 | |||
| 46 | IntermediateSymbol packagePayload = null; | ||
| 47 | foreach (var wixGroup in wixGroupPackagesGroupedById[id]) | ||
| 48 | { | ||
| 49 | if (wixGroup.ChildType == ComplexReferenceChildType.PackagePayload) | ||
| 50 | { | ||
| 51 | IntermediateSymbol tempPackagePayload = null; | ||
| 52 | if (exePackagePayloads.TryGetValue(wixGroup.ChildId, out var exePackagePayload)) | ||
| 53 | { | ||
| 54 | if (package.Type == WixBundlePackageType.Exe) | ||
| 55 | { | ||
| 56 | tempPackagePayload = exePackagePayload; | ||
| 57 | } | ||
| 58 | else | ||
| 59 | { | ||
| 60 | this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(exePackagePayload.SourceLineNumbers, "Exe")); | ||
| 61 | this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | else if (msiPackagePayloads.TryGetValue(wixGroup.ChildId, out var msiPackagePayload)) | ||
| 65 | { | ||
| 66 | if (package.Type == WixBundlePackageType.Msi) | ||
| 67 | { | ||
| 68 | tempPackagePayload = msiPackagePayload; | ||
| 69 | } | ||
| 70 | else | ||
| 71 | { | ||
| 72 | this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msiPackagePayload.SourceLineNumbers, "Msi")); | ||
| 73 | this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | else if (mspPackagePayloads.TryGetValue(wixGroup.ChildId, out var mspPackagePayload)) | ||
| 77 | { | ||
| 78 | if (package.Type == WixBundlePackageType.Msp) | ||
| 79 | { | ||
| 80 | tempPackagePayload = mspPackagePayload; | ||
| 81 | } | ||
| 82 | else | ||
| 83 | { | ||
| 84 | this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(mspPackagePayload.SourceLineNumbers, "Msp")); | ||
| 85 | this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | else if (msuPackagePayloads.TryGetValue(wixGroup.ChildId, out var msuPackagePayload)) | ||
| 89 | { | ||
| 90 | if (package.Type == WixBundlePackageType.Msu) | ||
| 91 | { | ||
| 92 | tempPackagePayload = msuPackagePayload; | ||
| 93 | } | ||
| 94 | else | ||
| 95 | { | ||
| 96 | this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msuPackagePayload.SourceLineNumbers, "Msu")); | ||
| 97 | this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | else | ||
| 101 | { | ||
| 102 | this.Messaging.Write(ErrorMessages.IdentifierNotFound(package.Type + "PackagePayload", wixGroup.ChildId)); | ||
| 103 | } | ||
| 104 | |||
| 105 | if (tempPackagePayload != null) | ||
| 106 | { | ||
| 107 | if (packagePayload == null) | ||
| 108 | { | ||
| 109 | packagePayload = tempPackagePayload; | ||
| 110 | } | ||
| 111 | else | ||
| 112 | { | ||
| 113 | this.Messaging.Write(ErrorMessages.MultiplePackagePayloads(tempPackagePayload.SourceLineNumbers, id, packagePayload.Id.Id, tempPackagePayload.Id.Id)); | ||
| 114 | this.Messaging.Write(ErrorMessages.MultiplePackagePayloads2(packagePayload.SourceLineNumbers)); | ||
| 115 | this.Messaging.Write(ErrorMessages.MultiplePackagePayloads3(package.SourceLineNumbers)); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | if (packagePayload == null) | ||
| 122 | { | ||
| 123 | this.Messaging.Write(ErrorMessages.MissingPackagePayload(package.SourceLineNumbers, id, package.Type.ToString())); | ||
| 124 | } | ||
| 125 | else | ||
| 126 | { | ||
| 127 | package.PayloadRef = packagePayload.Id.Id; | ||
| 128 | } | ||
| 129 | |||
| 130 | switch (package.Type) | ||
| 131 | { | ||
| 132 | case WixBundlePackageType.Exe: | ||
| 133 | if (exePackages.TryGetValue(id, out var exePackage)) | ||
| 134 | { | ||
| 135 | facades.Add(id, new PackageFacade(package, exePackage)); | ||
| 136 | } | ||
| 137 | else | ||
| 138 | { | ||
| 139 | this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleExePackage", id)); | ||
| 140 | } | ||
| 141 | break; | ||
| 142 | |||
| 143 | case WixBundlePackageType.Msi: | ||
| 144 | if (msiPackages.TryGetValue(id, out var msiPackage)) | ||
| 145 | { | ||
| 146 | facades.Add(id, new PackageFacade(package, msiPackage)); | ||
| 147 | } | ||
| 148 | else | ||
| 149 | { | ||
| 150 | this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsiPackage", id)); | ||
| 151 | } | ||
| 152 | break; | ||
| 153 | |||
| 154 | case WixBundlePackageType.Msp: | ||
| 155 | if (mspPackages.TryGetValue(id, out var mspPackage)) | ||
| 156 | { | ||
| 157 | facades.Add(id, new PackageFacade(package, mspPackage)); | ||
| 158 | } | ||
| 159 | else | ||
| 160 | { | ||
| 161 | this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMspPackage", id)); | ||
| 162 | } | ||
| 163 | break; | ||
| 164 | |||
| 165 | case WixBundlePackageType.Msu: | ||
| 166 | if (msuPackages.TryGetValue(id, out var msuPackage)) | ||
| 167 | { | ||
| 168 | facades.Add(id, new PackageFacade(package, msuPackage)); | ||
| 169 | } | ||
| 170 | else | ||
| 171 | { | ||
| 172 | this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsuPackage", id)); | ||
| 173 | } | ||
| 174 | break; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | this.PackageFacades = facades; | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs new file mode 100644 index 00000000..ccf6b1c2 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs | |||
| @@ -0,0 +1,171 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Burn; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class OrderPackagesAndRollbackBoundariesCommand | ||
| 14 | { | ||
| 15 | public OrderPackagesAndRollbackBoundariesCommand(IMessaging messaging, IntermediateSection section, IDictionary<string, PackageFacade> packageFacades) | ||
| 16 | { | ||
| 17 | this.Messaging = messaging; | ||
| 18 | this.Section = section; | ||
| 19 | this.PackageFacades = packageFacades; | ||
| 20 | } | ||
| 21 | |||
| 22 | private IMessaging Messaging { get; } | ||
| 23 | |||
| 24 | private IntermediateSection Section { get; } | ||
| 25 | |||
| 26 | private IDictionary<string, PackageFacade> PackageFacades { get; } | ||
| 27 | |||
| 28 | public IEnumerable<PackageFacade> OrderedPackageFacades { get; private set; } | ||
| 29 | |||
| 30 | public IEnumerable<WixBundleRollbackBoundarySymbol> UsedRollbackBoundaries { get; private set; } | ||
| 31 | |||
| 32 | public void Execute() | ||
| 33 | { | ||
| 34 | var groupSymbols = this.Section.Symbols.OfType<WixGroupSymbol>().ToList(); | ||
| 35 | var boundariesById = this.Section.Symbols.OfType<WixBundleRollbackBoundarySymbol>().ToDictionary(b => b.Id.Id); | ||
| 36 | |||
| 37 | var orderedFacades = new List<PackageFacade>(); | ||
| 38 | var usedBoundaries = new List<WixBundleRollbackBoundarySymbol>(); | ||
| 39 | |||
| 40 | // Process the chain of packages to add them in the correct order | ||
| 41 | // and assign the forward rollback boundaries as appropriate. Remember | ||
| 42 | // rollback boundaries are authored as elements in the chain which | ||
| 43 | // we re-interpret here to add them as attributes on the next available | ||
| 44 | // package in the chain. Essentially we mark some packages as being | ||
| 45 | // the start of a rollback boundary when installing and repairing. | ||
| 46 | // We handle uninstall (aka: backwards) rollback boundaries after | ||
| 47 | // we get these install/repair (aka: forward) rollback boundaries | ||
| 48 | // defined. | ||
| 49 | var pendingRollbackBoundary = new WixBundleRollbackBoundarySymbol(null, new Identifier(AccessModifier.Section, BurnConstants.BundleDefaultBoundaryId)) { Vital = true }; | ||
| 50 | var lastRollbackBoundary = pendingRollbackBoundary; | ||
| 51 | var boundaryHadX86Package = false; | ||
| 52 | var warnedMsiTransaction = false; | ||
| 53 | |||
| 54 | foreach (var groupSymbol in groupSymbols) | ||
| 55 | { | ||
| 56 | if (ComplexReferenceChildType.Package == groupSymbol.ChildType && ComplexReferenceParentType.PackageGroup == groupSymbol.ParentType && BurnConstants.BundleChainPackageGroupId == groupSymbol.ParentId) | ||
| 57 | { | ||
| 58 | if (this.PackageFacades.TryGetValue(groupSymbol.ChildId, out var facade)) | ||
| 59 | { | ||
| 60 | var insideMsiTransaction = lastRollbackBoundary?.Transaction ?? false; | ||
| 61 | |||
| 62 | if (null != pendingRollbackBoundary) | ||
| 63 | { | ||
| 64 | // If we used the default boundary, ensure the symbol is added to the section. | ||
| 65 | if (pendingRollbackBoundary.Id.Id == BurnConstants.BundleDefaultBoundaryId) | ||
| 66 | { | ||
| 67 | this.Section.AddSymbol(pendingRollbackBoundary); | ||
| 68 | } | ||
| 69 | |||
| 70 | if (insideMsiTransaction && !warnedMsiTransaction) | ||
| 71 | { | ||
| 72 | warnedMsiTransaction = true; | ||
| 73 | this.Messaging.Write(WarningMessages.MsiTransactionLimitations(pendingRollbackBoundary.SourceLineNumbers)); | ||
| 74 | } | ||
| 75 | |||
| 76 | usedBoundaries.Add(pendingRollbackBoundary); | ||
| 77 | facade.PackageSymbol.RollbackBoundaryRef = pendingRollbackBoundary.Id.Id; | ||
| 78 | pendingRollbackBoundary = null; | ||
| 79 | |||
| 80 | boundaryHadX86Package = !facade.PackageSymbol.Win64; | ||
| 81 | } | ||
| 82 | |||
| 83 | // Error if MSI transaction has x86 package preceding x64 packages | ||
| 84 | if (insideMsiTransaction && boundaryHadX86Package && facade.PackageSymbol.Win64) | ||
| 85 | { | ||
| 86 | this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(facade.PackageSymbol.SourceLineNumbers)); | ||
| 87 | } | ||
| 88 | |||
| 89 | boundaryHadX86Package |= !facade.PackageSymbol.Win64; | ||
| 90 | |||
| 91 | orderedFacades.Add(facade); | ||
| 92 | } | ||
| 93 | else // must be a rollback boundary. | ||
| 94 | { | ||
| 95 | // Discard the next rollback boundary if we have a previously defined boundary. | ||
| 96 | var nextRollbackBoundary = boundariesById[groupSymbol.ChildId]; | ||
| 97 | if (null != pendingRollbackBoundary) | ||
| 98 | { | ||
| 99 | if (pendingRollbackBoundary.Id.Id != BurnConstants.BundleDefaultBoundaryId) | ||
| 100 | { | ||
| 101 | this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id)); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | lastRollbackBoundary = pendingRollbackBoundary = nextRollbackBoundary; | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | if (null != pendingRollbackBoundary) | ||
| 111 | { | ||
| 112 | this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(pendingRollbackBoundary.SourceLineNumbers, pendingRollbackBoundary.Id.Id)); | ||
| 113 | } | ||
| 114 | |||
| 115 | // With the forward rollback boundaries assigned, we can now go | ||
| 116 | // through the packages with rollback boundaries and assign backward | ||
| 117 | // rollback boundaries. Backward rollback boundaries are used when | ||
| 118 | // the chain is going "backwards" which (AFAIK) only happens during | ||
| 119 | // uninstall. | ||
| 120 | // | ||
| 121 | // Consider the scenario with three packages: A, B and C. Packages A | ||
| 122 | // and C are marked as rollback boundary packages and package B is | ||
| 123 | // not. The naive implementation would execute the chain like this | ||
| 124 | // (numbers indicate where rollback boundaries would end up): | ||
| 125 | // install: 1 A B 2 C | ||
| 126 | // uninstall: 2 C B 1 A | ||
| 127 | // | ||
| 128 | // The uninstall chain is wrong, A and B should be grouped together | ||
| 129 | // not C and B. The fix is to label packages with a "backwards" | ||
| 130 | // rollback boundary used during uninstall. The backwards rollback | ||
| 131 | // boundaries are assigned to the package *before* the next rollback | ||
| 132 | // boundary. Using our example from above again, I'll mark the | ||
| 133 | // backwards rollback boundaries prime (aka: with '). | ||
| 134 | // install: 1 A B 1' 2 C 2' | ||
| 135 | // uninstall: 2' C 2 1' B A 1 | ||
| 136 | // | ||
| 137 | // If the marked boundaries are ignored during install you get the | ||
| 138 | // same thing as above (good) and if the non-marked boundaries are | ||
| 139 | // ignored during uninstall then A and B are correctly grouped. | ||
| 140 | // Here's what it looks like without all the markers: | ||
| 141 | // install: 1 A B 2 C | ||
| 142 | // uninstall: 2 C 1 B A | ||
| 143 | // Woot! | ||
| 144 | string previousRollbackBoundaryId = null; | ||
| 145 | PackageFacade previousFacade = null; | ||
| 146 | |||
| 147 | foreach (var package in orderedFacades) | ||
| 148 | { | ||
| 149 | if (null != package.PackageSymbol.RollbackBoundaryRef) | ||
| 150 | { | ||
| 151 | if (null != previousFacade) | ||
| 152 | { | ||
| 153 | previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; | ||
| 154 | } | ||
| 155 | |||
| 156 | previousRollbackBoundaryId = package.PackageSymbol.RollbackBoundaryRef; | ||
| 157 | } | ||
| 158 | |||
| 159 | previousFacade = package; | ||
| 160 | } | ||
| 161 | |||
| 162 | if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) | ||
| 163 | { | ||
| 164 | previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; | ||
| 165 | } | ||
| 166 | |||
| 167 | this.OrderedPackageFacades = orderedFacades; | ||
| 168 | this.UsedRollbackBoundaries = usedBoundaries; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs new file mode 100644 index 00000000..f3afd64e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs | |||
| @@ -0,0 +1,367 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Burn; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class OrderSearchesCommand | ||
| 15 | { | ||
| 16 | public OrderSearchesCommand(IMessaging messaging, IntermediateSection section) | ||
| 17 | { | ||
| 18 | this.Messaging = messaging; | ||
| 19 | this.Section = section; | ||
| 20 | } | ||
| 21 | |||
| 22 | private IMessaging Messaging { get; } | ||
| 23 | |||
| 24 | private IntermediateSection Section { get; } | ||
| 25 | |||
| 26 | public IDictionary<string, IEnumerable<IntermediateSymbol>> ExtensionSearchSymbolsByExtensionId { get; private set; } | ||
| 27 | |||
| 28 | public IEnumerable<ISearchFacade> OrderedSearchFacades { get; private set; } | ||
| 29 | |||
| 30 | public void Execute() | ||
| 31 | { | ||
| 32 | this.ExtensionSearchSymbolsByExtensionId = new Dictionary<string, IEnumerable<IntermediateSymbol>>(); | ||
| 33 | this.OrderedSearchFacades = Array.Empty<ISearchFacade>(); | ||
| 34 | |||
| 35 | var searchSymbols = this.Section.Symbols.OfType<WixSearchSymbol>().ToDictionary(t => t.Id.Id); | ||
| 36 | if (searchSymbols.Count == 0) | ||
| 37 | { | ||
| 38 | // Nothing to do! | ||
| 39 | return; | ||
| 40 | } | ||
| 41 | |||
| 42 | var constraints = new Constraints(); | ||
| 43 | |||
| 44 | // Add relational info to our data... | ||
| 45 | foreach (var searchRelationSymbol in this.Section.Symbols.OfType<WixSearchRelationSymbol>()) | ||
| 46 | { | ||
| 47 | constraints.AddConstraint(searchRelationSymbol.Id.Id, searchRelationSymbol.ParentSearchRef); | ||
| 48 | } | ||
| 49 | |||
| 50 | this.FindCircularReference(constraints); | ||
| 51 | |||
| 52 | if (this.Messaging.EncounteredError) | ||
| 53 | { | ||
| 54 | return; | ||
| 55 | } | ||
| 56 | |||
| 57 | this.FlattenDependentReferences(constraints); | ||
| 58 | |||
| 59 | // Reorder by topographical sort (http://en.wikipedia.org/wiki/Topological_sorting) | ||
| 60 | // We use a variation of Kahn (1962) algorithm as described in | ||
| 61 | // Wikipedia, with the additional criteria that start nodes are sorted | ||
| 62 | // lexicographically at each step to ensure a deterministic ordering | ||
| 63 | // based on 'after' dependencies and ID. | ||
| 64 | var sorter = new TopologicalSort(); | ||
| 65 | var sortedIds = sorter.Sort(searchSymbols.Keys, constraints); | ||
| 66 | |||
| 67 | // Now, create the search facades with the searches in order... | ||
| 68 | (var orderedSearchFacades, var extensionSearchSymbolsByExtensionId) = this.OrderSearches(sortedIds, searchSymbols); | ||
| 69 | |||
| 70 | this.OrderedSearchFacades = orderedSearchFacades; | ||
| 71 | this.ExtensionSearchSymbolsByExtensionId = extensionSearchSymbolsByExtensionId; | ||
| 72 | } | ||
| 73 | |||
| 74 | /// <summary> | ||
| 75 | /// A dictionary of constraints, mapping an id to a list of ids. | ||
| 76 | /// </summary> | ||
| 77 | private class Constraints : Dictionary<string, List<string>> | ||
| 78 | { | ||
| 79 | public void AddConstraint(string id, string afterId) | ||
| 80 | { | ||
| 81 | if (!this.ContainsKey(id)) | ||
| 82 | { | ||
| 83 | this.Add(id, new List<string>()); | ||
| 84 | } | ||
| 85 | |||
| 86 | // TODO: Show warning if a constraint is seen twice? | ||
| 87 | if (!this[id].Contains(afterId)) | ||
| 88 | { | ||
| 89 | this[id].Add(afterId); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | // TODO: Hide other Add methods? | ||
| 94 | } | ||
| 95 | |||
| 96 | /// <summary> | ||
| 97 | /// Finds circular references in the constraints. | ||
| 98 | /// </summary> | ||
| 99 | /// <param name="constraints">Constraints to check.</param> | ||
| 100 | /// <remarks>This is not particularly performant, but it works.</remarks> | ||
| 101 | private void FindCircularReference(Constraints constraints) | ||
| 102 | { | ||
| 103 | foreach (var id in constraints.Keys) | ||
| 104 | { | ||
| 105 | var seenIds = new List<string>(); | ||
| 106 | |||
| 107 | if (this.FindCircularReference(constraints, id, id, seenIds, out var chain)) | ||
| 108 | { | ||
| 109 | // We will show a separate message for every ID that's in | ||
| 110 | // the loop. We could bail after the first one, but then | ||
| 111 | // we wouldn't catch disjoint loops in a single run. | ||
| 112 | this.Messaging.Write(ErrorMessages.CircularSearchReference(chain)); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | /// <summary> | ||
| 118 | /// Recursive function that finds circular references in the constraints. | ||
| 119 | /// </summary> | ||
| 120 | /// <param name="constraints">Constraints to check.</param> | ||
| 121 | /// <param name="checkId">The identifier currently being looking for. (Fixed across a given run.)</param> | ||
| 122 | /// <param name="currentId">The idenifier curently being tested.</param> | ||
| 123 | /// <param name="seenIds">A list of identifiers seen, to ensure each identifier is only expanded once.</param> | ||
| 124 | /// <param name="chain">If a circular reference is found, will contain the chain of references.</param> | ||
| 125 | /// <returns>True if a circular reference is found, false otherwise.</returns> | ||
| 126 | private bool FindCircularReference(Constraints constraints, string checkId, string currentId, List<string> seenIds, out string chain) | ||
| 127 | { | ||
| 128 | chain = null; | ||
| 129 | if (constraints.TryGetValue(currentId, out var afterList)) | ||
| 130 | { | ||
| 131 | foreach (string afterId in afterList) | ||
| 132 | { | ||
| 133 | if (afterId == checkId) | ||
| 134 | { | ||
| 135 | chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, afterId); | ||
| 136 | return true; | ||
| 137 | } | ||
| 138 | |||
| 139 | if (!seenIds.Contains(afterId)) | ||
| 140 | { | ||
| 141 | seenIds.Add(afterId); | ||
| 142 | if (this.FindCircularReference(constraints, checkId, afterId, seenIds, out chain)) | ||
| 143 | { | ||
| 144 | chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, chain); | ||
| 145 | return true; | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | return false; | ||
| 152 | } | ||
| 153 | |||
| 154 | /// <summary> | ||
| 155 | /// Flattens any dependency chains to simplify reordering. | ||
| 156 | /// </summary> | ||
| 157 | /// <param name="constraints"></param> | ||
| 158 | private void FlattenDependentReferences(Constraints constraints) | ||
| 159 | { | ||
| 160 | foreach (string id in constraints.Keys) | ||
| 161 | { | ||
| 162 | var flattenedIds = new List<string>(); | ||
| 163 | this.AddDependentReferences(constraints, id, flattenedIds); | ||
| 164 | var constraintList = constraints[id]; | ||
| 165 | foreach (var flattenedId in flattenedIds) | ||
| 166 | { | ||
| 167 | if (!constraintList.Contains(flattenedId)) | ||
| 168 | { | ||
| 169 | constraintList.Add(flattenedId); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | /// <summary> | ||
| 176 | /// Adds dependent references to a list. | ||
| 177 | /// </summary> | ||
| 178 | /// <param name="constraints"></param> | ||
| 179 | /// <param name="currentId"></param> | ||
| 180 | /// <param name="seenIds"></param> | ||
| 181 | private void AddDependentReferences(Constraints constraints, string currentId, List<string> seenIds) | ||
| 182 | { | ||
| 183 | if (constraints.TryGetValue(currentId, out var afterList)) | ||
| 184 | { | ||
| 185 | foreach (var afterId in afterList) | ||
| 186 | { | ||
| 187 | if (!seenIds.Contains(afterId)) | ||
| 188 | { | ||
| 189 | seenIds.Add(afterId); | ||
| 190 | this.AddDependentReferences(constraints, afterId, seenIds); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | /// <summary> | ||
| 197 | /// Reorder by topological sort | ||
| 198 | /// </summary> | ||
| 199 | /// <remarks> | ||
| 200 | /// We use a variation of Kahn (1962) algorithm as described in | ||
| 201 | /// Wikipedia (http://en.wikipedia.org/wiki/Topological_sorting), with | ||
| 202 | /// the additional criteria that start nodes are sorted lexicographically | ||
| 203 | /// at each step to ensure a deterministic ordering based on 'after' | ||
| 204 | /// dependencies and ID. | ||
| 205 | /// </remarks> | ||
| 206 | private class TopologicalSort | ||
| 207 | { | ||
| 208 | private readonly List<string> startIds = new List<string>(); | ||
| 209 | private Constraints constraints; | ||
| 210 | |||
| 211 | /// <summary> | ||
| 212 | /// Reorder by topological sort | ||
| 213 | /// </summary> | ||
| 214 | /// <param name="allIds">The complete list of IDs.</param> | ||
| 215 | /// <param name="constraints">Constraints to use.</param> | ||
| 216 | /// <returns>The topologically sorted list of IDs.</returns> | ||
| 217 | internal List<string> Sort(IEnumerable<string> allIds, Constraints constraints) | ||
| 218 | { | ||
| 219 | this.startIds.Clear(); | ||
| 220 | this.CopyConstraints(constraints); | ||
| 221 | |||
| 222 | this.FindInitialStartIds(allIds); | ||
| 223 | |||
| 224 | // We always create a new sortedId list, because we return it | ||
| 225 | // to the caller and don't know what its lifetime may be. | ||
| 226 | var sortedIds = new List<string>(); | ||
| 227 | |||
| 228 | while (this.startIds.Count > 0) | ||
| 229 | { | ||
| 230 | this.SortStartIds(); | ||
| 231 | |||
| 232 | var currentId = this.startIds[0]; | ||
| 233 | sortedIds.Add(currentId); | ||
| 234 | this.startIds.RemoveAt(0); | ||
| 235 | |||
| 236 | this.ResolveConstraint(currentId); | ||
| 237 | } | ||
| 238 | |||
| 239 | return sortedIds; | ||
| 240 | } | ||
| 241 | |||
| 242 | /// <summary> | ||
| 243 | /// Copies a Constraints set (to prevent modifying the incoming data). | ||
| 244 | /// </summary> | ||
| 245 | /// <param name="constraints">Constraints to copy.</param> | ||
| 246 | private void CopyConstraints(Constraints constraints) | ||
| 247 | { | ||
| 248 | this.constraints = new Constraints(); | ||
| 249 | foreach (var id in constraints.Keys) | ||
| 250 | { | ||
| 251 | foreach (var afterId in constraints[id]) | ||
| 252 | { | ||
| 253 | this.constraints.AddConstraint(id, afterId); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | /// <summary> | ||
| 259 | /// Finds initial start IDs. (Those with no constraints.) | ||
| 260 | /// </summary> | ||
| 261 | /// <param name="allIds">The complete list of IDs.</param> | ||
| 262 | private void FindInitialStartIds(IEnumerable<string> allIds) | ||
| 263 | { | ||
| 264 | foreach (var id in allIds) | ||
| 265 | { | ||
| 266 | if (!this.constraints.ContainsKey(id)) | ||
| 267 | { | ||
| 268 | this.startIds.Add(id); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | /// <summary> | ||
| 274 | /// Sorts start IDs. | ||
| 275 | /// </summary> | ||
| 276 | private void SortStartIds() | ||
| 277 | { | ||
| 278 | this.startIds.Sort(); | ||
| 279 | } | ||
| 280 | |||
| 281 | /// <summary> | ||
| 282 | /// Removes the resolved constraint and updates the list of startIds | ||
| 283 | /// with any now-valid (all constraints resolved) IDs. | ||
| 284 | /// </summary> | ||
| 285 | /// <param name="resolvedId">The ID to resolve from the set of constraints.</param> | ||
| 286 | private void ResolveConstraint(string resolvedId) | ||
| 287 | { | ||
| 288 | var newStartIds = new List<string>(); | ||
| 289 | |||
| 290 | foreach (var id in this.constraints.Keys) | ||
| 291 | { | ||
| 292 | if (this.constraints[id].Contains(resolvedId)) | ||
| 293 | { | ||
| 294 | this.constraints[id].Remove(resolvedId); | ||
| 295 | |||
| 296 | // If we just removed the last constraint for this | ||
| 297 | // ID, it is now a valid start ID. | ||
| 298 | if (this.constraints[id].Count == 0) | ||
| 299 | { | ||
| 300 | newStartIds.Add(id); | ||
| 301 | } | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | foreach (var id in newStartIds) | ||
| 306 | { | ||
| 307 | this.constraints.Remove(id); | ||
| 308 | } | ||
| 309 | |||
| 310 | this.startIds.AddRange(newStartIds); | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | private (IEnumerable<ISearchFacade>, Dictionary<string, IEnumerable<IntermediateSymbol>>) OrderSearches(IEnumerable<string> sortedIds, Dictionary<string, WixSearchSymbol> searchSymbolDictionary) | ||
| 315 | { | ||
| 316 | var orderedSearchFacades = new List<ISearchFacade>(); | ||
| 317 | var extensionSearchSymbolsByExtensionId = new Dictionary<string, List<IntermediateSymbol>>(); | ||
| 318 | |||
| 319 | // TODO: Although the WixSearch tables are defined in the Util extension, | ||
| 320 | // the Bundle Binder has to know all about them. We hope to revisit all | ||
| 321 | // of this in the 4.0 timeframe. | ||
| 322 | var legacySearchesById = this.Section.Symbols | ||
| 323 | .Where(t => t.Definition.Type == SymbolDefinitionType.WixComponentSearch || | ||
| 324 | t.Definition.Type == SymbolDefinitionType.WixFileSearch || | ||
| 325 | t.Definition.Type == SymbolDefinitionType.WixProductSearch || | ||
| 326 | t.Definition.Type == SymbolDefinitionType.WixRegistrySearch) | ||
| 327 | .ToDictionary(t => t.Id.Id); | ||
| 328 | var setVariablesById = this.Section.Symbols | ||
| 329 | .OfType<WixSetVariableSymbol>() | ||
| 330 | .ToDictionary(t => t.Id.Id); | ||
| 331 | var extensionSearchesById = this.Section.Symbols | ||
| 332 | .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag)) | ||
| 333 | .ToDictionary(t => t.Id.Id); | ||
| 334 | |||
| 335 | foreach (var searchId in sortedIds) | ||
| 336 | { | ||
| 337 | var searchSymbol = searchSymbolDictionary[searchId]; | ||
| 338 | |||
| 339 | if (legacySearchesById.TryGetValue(searchId, out var specificSearchSymbol)) | ||
| 340 | { | ||
| 341 | orderedSearchFacades.Add(new LegacySearchFacade(searchSymbol, specificSearchSymbol)); | ||
| 342 | } | ||
| 343 | else if (setVariablesById.TryGetValue(searchId, out var setVariableSymbol)) | ||
| 344 | { | ||
| 345 | orderedSearchFacades.Add(new SetVariableSearchFacade(searchSymbol, setVariableSymbol)); | ||
| 346 | } | ||
| 347 | else if (extensionSearchesById.TryGetValue(searchId, out var extensionSearchSymbol)) | ||
| 348 | { | ||
| 349 | orderedSearchFacades.Add(new ExtensionSearchFacade(searchSymbol)); | ||
| 350 | |||
| 351 | if (!extensionSearchSymbolsByExtensionId.TryGetValue(searchSymbol.BundleExtensionRef, out var extensionSearchSymbols)) | ||
| 352 | { | ||
| 353 | extensionSearchSymbols = new List<IntermediateSymbol>(); | ||
| 354 | extensionSearchSymbolsByExtensionId[searchSymbol.BundleExtensionRef] = extensionSearchSymbols; | ||
| 355 | } | ||
| 356 | extensionSearchSymbols.Add(extensionSearchSymbol); | ||
| 357 | } | ||
| 358 | else | ||
| 359 | { | ||
| 360 | this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchSymbol.SourceLineNumbers, searchId)); | ||
| 361 | } | ||
| 362 | } | ||
| 363 | |||
| 364 | return (orderedSearchFacades, extensionSearchSymbolsByExtensionId.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable<IntermediateSymbol>)kvp.Value)); | ||
| 365 | } | ||
| 366 | } | ||
| 367 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs b/src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs new file mode 100644 index 00000000..471262de --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System.Diagnostics; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.Symbols; | ||
| 8 | |||
| 9 | internal class PackageFacade | ||
| 10 | { | ||
| 11 | public PackageFacade(WixBundlePackageSymbol packageSymbol, IntermediateSymbol specificPackageSymbol) | ||
| 12 | { | ||
| 13 | Debug.Assert(packageSymbol.Id.Id == specificPackageSymbol.Id.Id); | ||
| 14 | |||
| 15 | this.PackageSymbol = packageSymbol; | ||
| 16 | this.SpecificPackageSymbol = specificPackageSymbol; | ||
| 17 | } | ||
| 18 | |||
| 19 | public string PackageId => this.PackageSymbol.Id.Id; | ||
| 20 | |||
| 21 | public WixBundlePackageSymbol PackageSymbol { get; } | ||
| 22 | |||
| 23 | public IntermediateSymbol SpecificPackageSymbol { get; } | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs new file mode 100644 index 00000000..8d8ea986 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data.Symbols; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Initializes package state from the Exe contents. | ||
| 11 | /// </summary> | ||
| 12 | internal class ProcessExePackageCommand | ||
| 13 | { | ||
| 14 | public ProcessExePackageCommand(PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols) | ||
| 15 | { | ||
| 16 | this.AuthoredPayloads = payloadSymbols; | ||
| 17 | this.Facade = facade; | ||
| 18 | } | ||
| 19 | |||
| 20 | public Dictionary<string, WixBundlePayloadSymbol> AuthoredPayloads { get; } | ||
| 21 | |||
| 22 | public PackageFacade Facade { get; } | ||
| 23 | |||
| 24 | /// <summary> | ||
| 25 | /// Processes the Exe packages to add properties and payloads from the Exe packages. | ||
| 26 | /// </summary> | ||
| 27 | public void Execute() | ||
| 28 | { | ||
| 29 | var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; | ||
| 30 | |||
| 31 | if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) | ||
| 32 | { | ||
| 33 | this.Facade.PackageSymbol.CacheId = packagePayload.Hash; | ||
| 34 | } | ||
| 35 | |||
| 36 | this.Facade.PackageSymbol.Version = packagePayload.Version; | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs new file mode 100644 index 00000000..99e2eda5 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs | |||
| @@ -0,0 +1,558 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Extensibility; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Data.WindowsInstaller; | ||
| 15 | using WixToolset.Extensibility.Data; | ||
| 16 | using WixToolset.Core.Native.Msi; | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// Initializes package state from the MSI contents. | ||
| 20 | /// </summary> | ||
| 21 | internal class ProcessMsiPackageCommand | ||
| 22 | { | ||
| 23 | private const string PropertySqlQuery = "SELECT `Value` FROM `Property` WHERE `Property` = ?"; | ||
| 24 | |||
| 25 | public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable<IBurnBackendBinderExtension> backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> packagePayloads) | ||
| 26 | { | ||
| 27 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 28 | this.BackendHelper = serviceProvider.GetService<IBackendHelper>(); | ||
| 29 | this.PathResolver = serviceProvider.GetService<IPathResolver>(); | ||
| 30 | |||
| 31 | this.BackendExtensions = backendExtensions; | ||
| 32 | |||
| 33 | this.PackagePayloads = packagePayloads; | ||
| 34 | this.Section = section; | ||
| 35 | this.Facade = facade; | ||
| 36 | } | ||
| 37 | |||
| 38 | private IMessaging Messaging { get; } | ||
| 39 | |||
| 40 | private IBackendHelper BackendHelper { get; } | ||
| 41 | |||
| 42 | private IPathResolver PathResolver { get; } | ||
| 43 | |||
| 44 | private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; } | ||
| 45 | |||
| 46 | private Dictionary<string, WixBundlePayloadSymbol> PackagePayloads { get; } | ||
| 47 | |||
| 48 | private PackageFacade Facade { get; } | ||
| 49 | |||
| 50 | private IntermediateSection Section { get; } | ||
| 51 | |||
| 52 | /// <summary> | ||
| 53 | /// Processes the MSI packages to add properties and payloads from the MSI packages. | ||
| 54 | /// </summary> | ||
| 55 | public void Execute() | ||
| 56 | { | ||
| 57 | var packagePayload = this.PackagePayloads[this.Facade.PackageSymbol.PayloadRef]; | ||
| 58 | |||
| 59 | var msiPackage = (WixBundleMsiPackageSymbol)this.Facade.SpecificPackageSymbol; | ||
| 60 | |||
| 61 | var sourcePath = packagePayload.SourceFile.Path; | ||
| 62 | var longNamesInImage = false; | ||
| 63 | var compressed = false; | ||
| 64 | try | ||
| 65 | { | ||
| 66 | using (var db = new Database(sourcePath, OpenDatabase.ReadOnly)) | ||
| 67 | { | ||
| 68 | // Read data out of the msi database... | ||
| 69 | using (var sumInfo = new SummaryInformation(db)) | ||
| 70 | { | ||
| 71 | var fileAndElevateFlags = sumInfo.GetNumericProperty(SummaryInformation.Package.FileAndElevatedFlags); | ||
| 72 | var platformsAndLanguages = sumInfo.GetProperty(SummaryInformation.Package.PlatformsAndLanguages); | ||
| 73 | |||
| 74 | // 1 is the Word Count summary information stream bit that means | ||
| 75 | // the MSI uses short file names when set. We care about long file | ||
| 76 | // names so check when the bit is not set. | ||
| 77 | |||
| 78 | longNamesInImage = 0 == (fileAndElevateFlags & 1); | ||
| 79 | |||
| 80 | // 2 is the Word Count summary information stream bit that means | ||
| 81 | // files are compressed in the MSI by default when the bit is set. | ||
| 82 | compressed = 2 == (fileAndElevateFlags & 2); | ||
| 83 | |||
| 84 | // 8 is the Word Count summary information stream bit that means | ||
| 85 | // "Elevated privileges are not required to install this package." | ||
| 86 | // in MSI 4.5 and below, if this bit is 0, elevation is required. | ||
| 87 | var perMachine = (0 == (fileAndElevateFlags & 8)); | ||
| 88 | var x64 = platformsAndLanguages.Contains("x64"); | ||
| 89 | |||
| 90 | this.Facade.PackageSymbol.PerMachine = perMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; | ||
| 91 | this.Facade.PackageSymbol.Win64 = x64; | ||
| 92 | } | ||
| 93 | |||
| 94 | string packageName = null; | ||
| 95 | string packageDescription = null; | ||
| 96 | string allusers = null; | ||
| 97 | string fastInstall = null; | ||
| 98 | string systemComponent = null; | ||
| 99 | |||
| 100 | using (var view = db.OpenView(PropertySqlQuery)) | ||
| 101 | { | ||
| 102 | packageName = ProcessMsiPackageCommand.GetProperty(view, "ProductName"); | ||
| 103 | packageDescription = ProcessMsiPackageCommand.GetProperty(view, "ARPCOMMENTS"); | ||
| 104 | allusers = ProcessMsiPackageCommand.GetProperty(view, "ALLUSERS"); | ||
| 105 | fastInstall = ProcessMsiPackageCommand.GetProperty(view, "MSIFASTINSTALL"); | ||
| 106 | systemComponent = ProcessMsiPackageCommand.GetProperty(view, "ARPSYSTEMCOMPONENT"); | ||
| 107 | |||
| 108 | msiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(view, "ProductCode"); | ||
| 109 | msiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(view, "UpgradeCode"); | ||
| 110 | msiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(view, "Manufacturer"); | ||
| 111 | msiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(view, "ProductLanguage"), CultureInfo.InvariantCulture); | ||
| 112 | msiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(view, "ProductVersion"); | ||
| 113 | } | ||
| 114 | |||
| 115 | if (!this.BackendHelper.IsValidFourPartVersion(msiPackage.ProductVersion)) | ||
| 116 | { | ||
| 117 | // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? | ||
| 118 | string version = null; | ||
| 119 | var versionParts = msiPackage.ProductVersion.Split('.'); | ||
| 120 | var count = versionParts.Length; | ||
| 121 | if (0 < count) | ||
| 122 | { | ||
| 123 | version = versionParts[0]; | ||
| 124 | for (var i = 1; i < 4 && i < count; ++i) | ||
| 125 | { | ||
| 126 | version = String.Concat(version, ".", versionParts[i]); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | if (!String.IsNullOrEmpty(version) && this.BackendHelper.IsValidFourPartVersion(version)) | ||
| 131 | { | ||
| 132 | this.Messaging.Write(WarningMessages.VersionTruncated(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath, version)); | ||
| 133 | msiPackage.ProductVersion = version; | ||
| 134 | } | ||
| 135 | else | ||
| 136 | { | ||
| 137 | this.Messaging.Write(ErrorMessages.InvalidProductVersion(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath)); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) | ||
| 142 | { | ||
| 143 | this.Facade.PackageSymbol.CacheId = String.Format("{0}v{1}", msiPackage.ProductCode, msiPackage.ProductVersion); | ||
| 144 | } | ||
| 145 | |||
| 146 | if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) | ||
| 147 | { | ||
| 148 | this.Facade.PackageSymbol.DisplayName = packageName; | ||
| 149 | } | ||
| 150 | |||
| 151 | if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) | ||
| 152 | { | ||
| 153 | this.Facade.PackageSymbol.Description = packageDescription; | ||
| 154 | } | ||
| 155 | |||
| 156 | if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Version)) | ||
| 157 | { | ||
| 158 | this.Facade.PackageSymbol.Version = msiPackage.ProductVersion; | ||
| 159 | } | ||
| 160 | |||
| 161 | var payloadNames = this.GetPayloadTargetNames(); | ||
| 162 | |||
| 163 | var msiPropertyNames = this.GetMsiPropertyNames(packagePayload.Id.Id); | ||
| 164 | |||
| 165 | this.SetPerMachineAppropriately(allusers, msiPackage, sourcePath); | ||
| 166 | |||
| 167 | // Ensure the MSI package is appropriately marked visible or not. | ||
| 168 | this.SetPackageVisibility(systemComponent, msiPackage, msiPropertyNames); | ||
| 169 | |||
| 170 | // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. | ||
| 171 | if (!String.IsNullOrEmpty(fastInstall)) | ||
| 172 | { | ||
| 173 | this.AddMsiProperty(msiPackage, "MSIFASTINSTALL", "7"); | ||
| 174 | } | ||
| 175 | |||
| 176 | this.CreateRelatedPackages(db); | ||
| 177 | |||
| 178 | // If feature selection is enabled, represent the Feature table in the manifest. | ||
| 179 | if ((msiPackage.Attributes & WixBundleMsiPackageAttributes.EnableFeatureSelection) == WixBundleMsiPackageAttributes.EnableFeatureSelection) | ||
| 180 | { | ||
| 181 | this.CreateMsiFeatures(db); | ||
| 182 | } | ||
| 183 | |||
| 184 | // Add all external cabinets as package payloads. | ||
| 185 | this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames); | ||
| 186 | |||
| 187 | // Add all external files as package payloads and calculate the total install size as the rollup of | ||
| 188 | // File table's sizes. | ||
| 189 | this.Facade.PackageSymbol.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); | ||
| 190 | |||
| 191 | // Add all dependency providers from the MSI. | ||
| 192 | this.ImportDependencyProviders(db, msiPackage); | ||
| 193 | } | ||
| 194 | } | ||
| 195 | catch (MsiException e) | ||
| 196 | { | ||
| 197 | this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, e.Message)); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | private ISet<string> GetPayloadTargetNames() | ||
| 202 | { | ||
| 203 | var payloadNames = this.PackagePayloads.Values.Select(p => p.Name); | ||
| 204 | |||
| 205 | return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase); | ||
| 206 | } | ||
| 207 | |||
| 208 | private ISet<string> GetMsiPropertyNames(string packageId) | ||
| 209 | { | ||
| 210 | var properties = this.Section.Symbols.OfType<WixBundleMsiPropertySymbol>() | ||
| 211 | .Where(p => p.PackageRef == packageId) | ||
| 212 | .Select(p => p.Name); | ||
| 213 | |||
| 214 | return new HashSet<string>(properties, StringComparer.Ordinal); | ||
| 215 | } | ||
| 216 | |||
| 217 | private void SetPerMachineAppropriately(string allusers, WixBundleMsiPackageSymbol msiPackage, string sourcePath) | ||
| 218 | { | ||
| 219 | if (msiPackage.ForcePerMachine) | ||
| 220 | { | ||
| 221 | if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) | ||
| 222 | { | ||
| 223 | this.Messaging.Write(WarningMessages.PerUserButForcingPerMachine(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); | ||
| 224 | this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. | ||
| 225 | } | ||
| 226 | |||
| 227 | // Force ALLUSERS=1 via the MSI command-line. | ||
| 228 | this.AddMsiProperty(msiPackage, "ALLUSERS", "1"); | ||
| 229 | } | ||
| 230 | else | ||
| 231 | { | ||
| 232 | if (String.IsNullOrEmpty(allusers)) | ||
| 233 | { | ||
| 234 | // Not forced per-machine and no ALLUSERS property, flip back to per-user. | ||
| 235 | if (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) | ||
| 236 | { | ||
| 237 | this.Messaging.Write(WarningMessages.ImplicitlyPerUser(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); | ||
| 238 | this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.No; | ||
| 239 | } | ||
| 240 | } | ||
| 241 | else if (allusers.Equals("1", StringComparison.Ordinal)) | ||
| 242 | { | ||
| 243 | if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) | ||
| 244 | { | ||
| 245 | this.Messaging.Write(ErrorMessages.PerUserButAllUsersEquals1(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | else if (allusers.Equals("2", StringComparison.Ordinal)) | ||
| 249 | { | ||
| 250 | this.Messaging.Write(WarningMessages.DiscouragedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) ? "machine" : "user")); | ||
| 251 | } | ||
| 252 | else | ||
| 253 | { | ||
| 254 | this.Messaging.Write(ErrorMessages.UnsupportedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, allusers)); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | private void SetPackageVisibility(string systemComponent, WixBundleMsiPackageSymbol msiPackage, ISet<string> msiPropertyNames) | ||
| 260 | { | ||
| 261 | // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. | ||
| 262 | if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) | ||
| 263 | { | ||
| 264 | var alreadyVisible = String.IsNullOrEmpty(systemComponent); | ||
| 265 | var visible = (this.Facade.PackageSymbol.Attributes & WixBundlePackageAttributes.Visible) == WixBundlePackageAttributes.Visible; | ||
| 266 | |||
| 267 | // If not already set to the correct visibility. | ||
| 268 | if (alreadyVisible != visible) | ||
| 269 | { | ||
| 270 | this.AddMsiProperty(msiPackage, "ARPSYSTEMCOMPONENT", visible ? String.Empty : "1"); | ||
| 271 | } | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | private void CreateRelatedPackages(Database db) | ||
| 276 | { | ||
| 277 | // Represent the Upgrade table as related packages. | ||
| 278 | if (db.TableExists("Upgrade")) | ||
| 279 | { | ||
| 280 | using (var view = db.OpenExecuteView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) | ||
| 281 | { | ||
| 282 | foreach (var record in view.Records) | ||
| 283 | { | ||
| 284 | var recordAttributes = record.GetInteger(5); | ||
| 285 | |||
| 286 | var attributes = WixBundleRelatedPackageAttributes.None; | ||
| 287 | attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect ? WixBundleRelatedPackageAttributes.OnlyDetect : 0; | ||
| 288 | attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive ? WixBundleRelatedPackageAttributes.MinInclusive : 0; | ||
| 289 | attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive ? WixBundleRelatedPackageAttributes.MaxInclusive : 0; | ||
| 290 | attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive ? WixBundleRelatedPackageAttributes.LangInclusive : 0; | ||
| 291 | |||
| 292 | this.Section.AddSymbol(new WixBundleRelatedPackageSymbol(this.Facade.PackageSymbol.SourceLineNumbers) | ||
| 293 | { | ||
| 294 | PackageRef = this.Facade.PackageId, | ||
| 295 | RelatedId = record.GetString(1), | ||
| 296 | MinVersion = record.GetString(2), | ||
| 297 | MaxVersion = record.GetString(3), | ||
| 298 | Languages = record.GetString(4), | ||
| 299 | Attributes = attributes, | ||
| 300 | }); | ||
| 301 | } | ||
| 302 | } | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | private void CreateMsiFeatures(Database db) | ||
| 307 | { | ||
| 308 | if (db.TableExists("Feature")) | ||
| 309 | { | ||
| 310 | using (var allFeaturesView = db.OpenExecuteView("SELECT * FROM `Feature`")) | ||
| 311 | using (var featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) | ||
| 312 | using (var componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) | ||
| 313 | { | ||
| 314 | using (var featureRecord = new Record(1)) | ||
| 315 | using (var componentRecord = new Record(1)) | ||
| 316 | { | ||
| 317 | foreach (var allFeaturesResultRecord in allFeaturesView.Records) | ||
| 318 | { | ||
| 319 | var featureName = allFeaturesResultRecord.GetString(1); | ||
| 320 | |||
| 321 | // Calculate the Feature size. | ||
| 322 | featureRecord.SetString(1, featureName); | ||
| 323 | featureView.Execute(featureRecord); | ||
| 324 | |||
| 325 | // Loop over all the components for the feature to calculate the size of the feature. | ||
| 326 | long size = 0; | ||
| 327 | foreach (var componentResultRecord in featureView.Records) | ||
| 328 | { | ||
| 329 | var component = componentResultRecord.GetString(1); | ||
| 330 | componentRecord.SetString(1, component); | ||
| 331 | componentView.Execute(componentRecord); | ||
| 332 | |||
| 333 | foreach (var fileResultRecord in componentView.Records) | ||
| 334 | { | ||
| 335 | var fileSize = fileResultRecord.GetString(1); | ||
| 336 | size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 340 | this.Section.AddSymbol(new WixBundleMsiFeatureSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, this.Facade.PackageId, featureName)) | ||
| 341 | { | ||
| 342 | PackageRef = this.Facade.PackageId, | ||
| 343 | Name = featureName, | ||
| 344 | Parent = allFeaturesResultRecord.GetString(2), | ||
| 345 | Title = allFeaturesResultRecord.GetString(3), | ||
| 346 | Description = allFeaturesResultRecord.GetString(4), | ||
| 347 | Display = allFeaturesResultRecord.GetInteger(5), | ||
| 348 | Level = allFeaturesResultRecord.GetInteger(6), | ||
| 349 | Directory = allFeaturesResultRecord.GetString(7), | ||
| 350 | Attributes = allFeaturesResultRecord.GetInteger(8), | ||
| 351 | Size = size | ||
| 352 | }); | ||
| 353 | } | ||
| 354 | } | ||
| 355 | } | ||
| 356 | } | ||
| 357 | } | ||
| 358 | |||
| 359 | private void ImportExternalCabinetAsPayloads(Database db, WixBundlePayloadSymbol packagePayload, ISet<string> payloadNames) | ||
| 360 | { | ||
| 361 | if (db.TableExists("Media")) | ||
| 362 | { | ||
| 363 | using (var view = db.OpenExecuteView("SELECT `Cabinet` FROM `Media`")) | ||
| 364 | { | ||
| 365 | foreach (var cabinetRecord in view.Records) | ||
| 366 | { | ||
| 367 | var cabinet = cabinetRecord.GetString(1); | ||
| 368 | |||
| 369 | if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
| 370 | { | ||
| 371 | // If we didn't find the Payload as an existing child of the package, we need to | ||
| 372 | // add it. We expect the file to exist on-disk in the same relative location as | ||
| 373 | // the MSI expects to find it... | ||
| 374 | var cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); | ||
| 375 | |||
| 376 | if (!payloadNames.Contains(cabinetName)) | ||
| 377 | { | ||
| 378 | var generatedId = this.BackendHelper.GenerateIdentifier("cab", packagePayload.Id.Id, cabinet); | ||
| 379 | var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.PackageSymbol.SourceLineNumbers); | ||
| 380 | |||
| 381 | this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) | ||
| 382 | { | ||
| 383 | ParentType = ComplexReferenceParentType.Package, | ||
| 384 | ParentId = this.Facade.PackageId, | ||
| 385 | ChildType = ComplexReferenceChildType.Payload, | ||
| 386 | ChildId = generatedId | ||
| 387 | }); | ||
| 388 | |||
| 389 | this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) | ||
| 390 | { | ||
| 391 | Name = cabinetName, | ||
| 392 | SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, | ||
| 393 | Compressed = packagePayload.Compressed, | ||
| 394 | UnresolvedSourceFile = cabinetName, | ||
| 395 | ContainerRef = packagePayload.ContainerRef, | ||
| 396 | ContentFile = true, | ||
| 397 | Packaging = packagePayload.Packaging, | ||
| 398 | ParentPackagePayloadRef = packagePayload.Id.Id, | ||
| 399 | }); | ||
| 400 | } | ||
| 401 | } | ||
| 402 | } | ||
| 403 | } | ||
| 404 | } | ||
| 405 | } | ||
| 406 | |||
| 407 | private long ImportExternalFileAsPayloadsAndReturnInstallSize(Database db, WixBundlePayloadSymbol packagePayload, bool longNamesInImage, bool compressed, ISet<string> payloadNames) | ||
| 408 | { | ||
| 409 | long size = 0; | ||
| 410 | |||
| 411 | if (db.TableExists("Component") && db.TableExists("Directory") && db.TableExists("File")) | ||
| 412 | { | ||
| 413 | var directories = new Dictionary<string, IResolvedDirectory>(); | ||
| 414 | |||
| 415 | // Load up the directory hash table so we will be able to resolve source paths | ||
| 416 | // for files in the MSI database. | ||
| 417 | using (var view = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) | ||
| 418 | { | ||
| 419 | foreach (var record in view.Records) | ||
| 420 | { | ||
| 421 | var sourceName = this.BackendHelper.GetMsiFileName(record.GetString(3), true, longNamesInImage); | ||
| 422 | |||
| 423 | var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(record.GetString(2), sourceName); | ||
| 424 | |||
| 425 | directories.Add(record.GetString(1), resolvedDirectory); | ||
| 426 | } | ||
| 427 | } | ||
| 428 | |||
| 429 | // Resolve the source paths to external files and add each file size to the total | ||
| 430 | // install size of the package. | ||
| 431 | using (var view = db.OpenExecuteView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) | ||
| 432 | { | ||
| 433 | foreach (var record in view.Records) | ||
| 434 | { | ||
| 435 | // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not | ||
| 436 | // explicitly marked compressed then this is an external file. | ||
| 437 | var compressionBit = record.GetInteger(4); | ||
| 438 | if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) || | ||
| 439 | (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed))) | ||
| 440 | { | ||
| 441 | var fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); | ||
| 442 | var name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); | ||
| 443 | |||
| 444 | if (!payloadNames.Contains(name)) | ||
| 445 | { | ||
| 446 | var generatedId = this.BackendHelper.GenerateIdentifier("f", packagePayload.Id.Id, record.GetString(2)); | ||
| 447 | var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.PackageSymbol.SourceLineNumbers); | ||
| 448 | |||
| 449 | this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) | ||
| 450 | { | ||
| 451 | ParentType = ComplexReferenceParentType.Package, | ||
| 452 | ParentId = this.Facade.PackageId, | ||
| 453 | ChildType = ComplexReferenceChildType.Payload, | ||
| 454 | ChildId = generatedId | ||
| 455 | }); | ||
| 456 | |||
| 457 | this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) | ||
| 458 | { | ||
| 459 | Name = name, | ||
| 460 | SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, | ||
| 461 | Compressed = packagePayload.Compressed, | ||
| 462 | UnresolvedSourceFile = name, | ||
| 463 | ContainerRef = packagePayload.ContainerRef, | ||
| 464 | ContentFile = true, | ||
| 465 | Packaging = packagePayload.Packaging, | ||
| 466 | ParentPackagePayloadRef = packagePayload.Id.Id, | ||
| 467 | }); | ||
| 468 | } | ||
| 469 | } | ||
| 470 | |||
| 471 | size += record.GetInteger(5); | ||
| 472 | } | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | return size; | ||
| 477 | } | ||
| 478 | |||
| 479 | private void AddMsiProperty(WixBundleMsiPackageSymbol msiPackage, string name, string value) | ||
| 480 | { | ||
| 481 | this.Section.AddSymbol(new WixBundleMsiPropertySymbol(msiPackage.SourceLineNumbers, new Identifier(AccessModifier.Section, msiPackage.Id.Id, name)) | ||
| 482 | { | ||
| 483 | PackageRef = msiPackage.Id.Id, | ||
| 484 | Name = name, | ||
| 485 | Value = value, | ||
| 486 | }); | ||
| 487 | } | ||
| 488 | |||
| 489 | private void ImportDependencyProviders(Database db, WixBundleMsiPackageSymbol msiPackage) | ||
| 490 | { | ||
| 491 | if (db.TableExists("WixDependencyProvider")) | ||
| 492 | { | ||
| 493 | using (var view = db.OpenExecuteView("SELECT `WixDependencyProvider`, `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`")) | ||
| 494 | { | ||
| 495 | foreach (var record in view.Records) | ||
| 496 | { | ||
| 497 | var id = new Identifier(AccessModifier.Section, this.BackendHelper.GenerateIdentifier("dep", msiPackage.Id.Id, record.GetString(1))); | ||
| 498 | |||
| 499 | // Import the provider key and attributes. | ||
| 500 | this.Section.AddSymbol(new WixDependencyProviderSymbol(msiPackage.SourceLineNumbers, id) | ||
| 501 | { | ||
| 502 | ParentRef = msiPackage.Id.Id, | ||
| 503 | ProviderKey = record.GetString(2), | ||
| 504 | Version = record.GetString(3) ?? msiPackage.ProductVersion, | ||
| 505 | DisplayName = record.GetString(4) ?? this.Facade.PackageSymbol.DisplayName, | ||
| 506 | Attributes = WixDependencyProviderAttributes.ProvidesAttributesImported | (WixDependencyProviderAttributes)record.GetInteger(5), | ||
| 507 | }); | ||
| 508 | } | ||
| 509 | } | ||
| 510 | } | ||
| 511 | } | ||
| 512 | |||
| 513 | private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers) | ||
| 514 | { | ||
| 515 | var checkedPaths = new List<string>(); | ||
| 516 | |||
| 517 | foreach (var extension in this.BackendExtensions) | ||
| 518 | { | ||
| 519 | var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers); | ||
| 520 | |||
| 521 | if (resolved?.CheckedPaths != null) | ||
| 522 | { | ||
| 523 | checkedPaths.AddRange(resolved.CheckedPaths); | ||
| 524 | } | ||
| 525 | |||
| 526 | if (!String.IsNullOrEmpty(resolved?.Path)) | ||
| 527 | { | ||
| 528 | return resolved?.Path; | ||
| 529 | } | ||
| 530 | } | ||
| 531 | |||
| 532 | var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource); | ||
| 533 | |||
| 534 | if (!File.Exists(resolvedPath)) | ||
| 535 | { | ||
| 536 | checkedPaths.Add(resolvedPath); | ||
| 537 | this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths)); | ||
| 538 | } | ||
| 539 | |||
| 540 | return resolvedPath; | ||
| 541 | } | ||
| 542 | |||
| 543 | private static string GetProperty(View view, string property) | ||
| 544 | { | ||
| 545 | using (var queryRecord = new Record(1)) | ||
| 546 | { | ||
| 547 | queryRecord[1] = property; | ||
| 548 | |||
| 549 | view.Execute(queryRecord); | ||
| 550 | |||
| 551 | using (var record = view.Fetch()) | ||
| 552 | { | ||
| 553 | return record?.GetString(1); | ||
| 554 | } | ||
| 555 | } | ||
| 556 | } | ||
| 557 | } | ||
| 558 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs new file mode 100644 index 00000000..5f431b38 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs | |||
| @@ -0,0 +1,183 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Text; | ||
| 9 | using System.Xml; | ||
| 10 | using WixToolset.Core.Native.Msi; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Initializes package state from the Msp contents. | ||
| 17 | /// </summary> | ||
| 18 | internal class ProcessMspPackageCommand | ||
| 19 | { | ||
| 20 | private const string PatchMetadataQuery = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = ?"; | ||
| 21 | private static readonly XmlWriterSettings XmlSettings = new XmlWriterSettings() | ||
| 22 | { | ||
| 23 | Encoding = new UTF8Encoding(false), | ||
| 24 | Indent = false, | ||
| 25 | NewLineChars = String.Empty, | ||
| 26 | NewLineHandling = NewLineHandling.Replace, | ||
| 27 | }; | ||
| 28 | |||
| 29 | public ProcessMspPackageCommand(IMessaging messaging, IntermediateSection section, PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols) | ||
| 30 | { | ||
| 31 | this.Messaging = messaging; | ||
| 32 | |||
| 33 | this.AuthoredPayloads = payloadSymbols; | ||
| 34 | this.Section = section; | ||
| 35 | this.Facade = facade; | ||
| 36 | } | ||
| 37 | |||
| 38 | public IMessaging Messaging { get; } | ||
| 39 | |||
| 40 | public Dictionary<string, WixBundlePayloadSymbol> AuthoredPayloads { private get; set; } | ||
| 41 | |||
| 42 | public PackageFacade Facade { private get; set; } | ||
| 43 | |||
| 44 | public IntermediateSection Section { get; } | ||
| 45 | |||
| 46 | /// <summary> | ||
| 47 | /// Processes the Msp packages to add properties and payloads from the Msp packages. | ||
| 48 | /// </summary> | ||
| 49 | public void Execute() | ||
| 50 | { | ||
| 51 | var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; | ||
| 52 | |||
| 53 | var mspPackage = (WixBundleMspPackageSymbol)this.Facade.SpecificPackageSymbol; | ||
| 54 | |||
| 55 | var sourcePath = packagePayload.SourceFile.Path; | ||
| 56 | |||
| 57 | try | ||
| 58 | { | ||
| 59 | using (var db = new Database(sourcePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) | ||
| 60 | { | ||
| 61 | // Read data out of the msp database... | ||
| 62 | using (var sumInfo = new SummaryInformation(db)) | ||
| 63 | { | ||
| 64 | var patchCode = sumInfo.GetProperty(SummaryInformation.Patch.PatchCode); | ||
| 65 | mspPackage.PatchCode = patchCode.Substring(0, 38); | ||
| 66 | } | ||
| 67 | |||
| 68 | using (var view = db.OpenView(PatchMetadataQuery)) | ||
| 69 | { | ||
| 70 | if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) | ||
| 71 | { | ||
| 72 | this.Facade.PackageSymbol.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "DisplayName"); | ||
| 73 | } | ||
| 74 | |||
| 75 | if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) | ||
| 76 | { | ||
| 77 | this.Facade.PackageSymbol.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "Description"); | ||
| 78 | } | ||
| 79 | |||
| 80 | mspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "ManufacturerName"); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | this.ProcessPatchXml(packagePayload, mspPackage, sourcePath); | ||
| 85 | } | ||
| 86 | catch (MsiException e) | ||
| 87 | { | ||
| 88 | this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); | ||
| 89 | return; | ||
| 90 | } | ||
| 91 | |||
| 92 | if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) | ||
| 93 | { | ||
| 94 | this.Facade.PackageSymbol.CacheId = mspPackage.PatchCode; | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | private void ProcessPatchXml(WixBundlePayloadSymbol packagePayload, WixBundleMspPackageSymbol mspPackage, string sourcePath) | ||
| 99 | { | ||
| 100 | var uniqueTargetCodes = new HashSet<string>(); | ||
| 101 | |||
| 102 | var patchXml = Installer.ExtractPatchXml(sourcePath); | ||
| 103 | |||
| 104 | var doc = new XmlDocument(); | ||
| 105 | doc.LoadXml(patchXml); | ||
| 106 | |||
| 107 | var nsmgr = new XmlNamespaceManager(doc.NameTable); | ||
| 108 | nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); | ||
| 109 | |||
| 110 | // Determine target ProductCodes and/or UpgradeCodes. | ||
| 111 | foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) | ||
| 112 | { | ||
| 113 | // If this patch targets a product code, this is the best case. | ||
| 114 | var targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); | ||
| 115 | var attributes = WixBundlePatchTargetCodeAttributes.None; | ||
| 116 | |||
| 117 | if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) | ||
| 118 | { | ||
| 119 | attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; | ||
| 120 | } | ||
| 121 | else // maybe targets an upgrade code? | ||
| 122 | { | ||
| 123 | targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr); | ||
| 124 | if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) | ||
| 125 | { | ||
| 126 | attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; | ||
| 127 | } | ||
| 128 | else // this patch targets an unknown number of products | ||
| 129 | { | ||
| 130 | mspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | var targetCode = targetCodeElement.InnerText; | ||
| 135 | |||
| 136 | if (uniqueTargetCodes.Add(targetCode)) | ||
| 137 | { | ||
| 138 | this.Section.AddSymbol(new WixBundlePatchTargetCodeSymbol(packagePayload.SourceLineNumbers) | ||
| 139 | { | ||
| 140 | PackageRef = packagePayload.Id.Id, | ||
| 141 | TargetCode = targetCode, | ||
| 142 | Attributes = attributes | ||
| 143 | }); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | // Suppress patch sequence data for improved performance. | ||
| 148 | var root = doc.DocumentElement; | ||
| 149 | foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) | ||
| 150 | { | ||
| 151 | root.RemoveChild(node); | ||
| 152 | } | ||
| 153 | |||
| 154 | // Save the XML as compact as possible. | ||
| 155 | using (var writer = new StringWriter()) | ||
| 156 | { | ||
| 157 | using (var xmlWriter = XmlWriter.Create(writer, XmlSettings)) | ||
| 158 | { | ||
| 159 | doc.WriteTo(xmlWriter); | ||
| 160 | } | ||
| 161 | |||
| 162 | mspPackage.PatchXml = writer.ToString(); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | private static string GetPatchMetadataProperty(View view, string property) | ||
| 167 | { | ||
| 168 | using (var queryRecord = new Record(1)) | ||
| 169 | { | ||
| 170 | queryRecord[1] = property; | ||
| 171 | |||
| 172 | view.Execute(queryRecord); | ||
| 173 | |||
| 174 | using (var record = view.Fetch()) | ||
| 175 | { | ||
| 176 | return record?.GetString(1); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | private static bool TargetsCode(XmlNode node) => "true" == node?.Attributes["Validate"]?.Value; | ||
| 182 | } | ||
| 183 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs new file mode 100644 index 00000000..af4ab3a8 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Symbols; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Processes the Msu packages to add properties and payloads from the Msu packages. | ||
| 12 | /// </summary> | ||
| 13 | internal class ProcessMsuPackageCommand | ||
| 14 | { | ||
| 15 | public ProcessMsuPackageCommand(PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols) | ||
| 16 | { | ||
| 17 | this.AuthoredPayloads = payloadSymbols; | ||
| 18 | this.Facade = facade; | ||
| 19 | } | ||
| 20 | |||
| 21 | public Dictionary<string, WixBundlePayloadSymbol> AuthoredPayloads { private get; set; } | ||
| 22 | |||
| 23 | public PackageFacade Facade { private get; set; } | ||
| 24 | |||
| 25 | public void Execute() | ||
| 26 | { | ||
| 27 | var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; | ||
| 28 | |||
| 29 | if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) | ||
| 30 | { | ||
| 31 | this.Facade.PackageSymbol.CacheId = packagePayload.Hash; | ||
| 32 | } | ||
| 33 | |||
| 34 | this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs new file mode 100644 index 00000000..fa70251a --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.IO; | ||
| 9 | using WixToolset.Core.Burn.Interfaces; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Burn; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Extensibility.Data; | ||
| 14 | using WixToolset.Extensibility.Services; | ||
| 15 | |||
| 16 | internal class ProcessPayloadsCommand | ||
| 17 | { | ||
| 18 | public ProcessPayloadsCommand(IBackendHelper backendHelper, IPayloadHarvester payloadHarvester, IEnumerable<WixBundlePayloadSymbol> payloads, PackagingType defaultPackaging, string layoutDirectory) | ||
| 19 | { | ||
| 20 | this.BackendHelper = backendHelper; | ||
| 21 | this.PayloadHarvester = payloadHarvester; | ||
| 22 | this.Payloads = payloads; | ||
| 23 | this.DefaultPackaging = defaultPackaging; | ||
| 24 | this.LayoutDirectory = layoutDirectory; | ||
| 25 | } | ||
| 26 | |||
| 27 | public IEnumerable<IFileTransfer> FileTransfers { get; private set; } | ||
| 28 | |||
| 29 | public IEnumerable<ITrackedFile> TrackedFiles { get; private set; } | ||
| 30 | |||
| 31 | private IBackendHelper BackendHelper { get; } | ||
| 32 | |||
| 33 | private IPayloadHarvester PayloadHarvester { get; } | ||
| 34 | |||
| 35 | private IEnumerable<WixBundlePayloadSymbol> Payloads { get; } | ||
| 36 | |||
| 37 | private PackagingType DefaultPackaging { get; } | ||
| 38 | |||
| 39 | private string LayoutDirectory { get; } | ||
| 40 | |||
| 41 | public void Execute() | ||
| 42 | { | ||
| 43 | var fileTransfers = new List<IFileTransfer>(); | ||
| 44 | var trackedFiles = new List<ITrackedFile>(); | ||
| 45 | |||
| 46 | foreach (var payload in this.Payloads) | ||
| 47 | { | ||
| 48 | payload.Name = this.BackendHelper.GetCanonicalRelativePath(payload.SourceLineNumbers, "Payload", "Name", payload.Name); | ||
| 49 | |||
| 50 | // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden | ||
| 51 | // in the .wixlib). | ||
| 52 | var sourceFile = payload.SourceFile; | ||
| 53 | payload.ContentFile = sourceFile != null && !sourceFile.Embed; | ||
| 54 | |||
| 55 | this.UpdatePayloadPackagingType(payload); | ||
| 56 | |||
| 57 | if (!this.PayloadHarvester.HarvestStandardInformation(payload)) | ||
| 58 | { | ||
| 59 | // Remote payloads obviously cannot be embedded. | ||
| 60 | Debug.Assert(PackagingType.Embedded != payload.Packaging); | ||
| 61 | } | ||
| 62 | else // not a remote payload so we have a lot more to update. | ||
| 63 | { | ||
| 64 | // External payloads need to be transfered. | ||
| 65 | if (PackagingType.External == payload.Packaging) | ||
| 66 | { | ||
| 67 | var transfer = this.BackendHelper.CreateFileTransfer(sourceFile.Path, Path.Combine(this.LayoutDirectory, payload.Name), false, payload.SourceLineNumbers); | ||
| 68 | fileTransfers.Add(transfer); | ||
| 69 | } | ||
| 70 | |||
| 71 | if (payload.ContentFile) | ||
| 72 | { | ||
| 73 | trackedFiles.Add(this.BackendHelper.TrackFile(sourceFile.Path, TrackedFileType.Input, payload.SourceLineNumbers)); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | this.FileTransfers = fileTransfers; | ||
| 79 | this.TrackedFiles = trackedFiles; | ||
| 80 | } | ||
| 81 | |||
| 82 | private void UpdatePayloadPackagingType(WixBundlePayloadSymbol payload) | ||
| 83 | { | ||
| 84 | if (!payload.Packaging.HasValue || PackagingType.Unknown == payload.Packaging) | ||
| 85 | { | ||
| 86 | if (!payload.Compressed.HasValue) | ||
| 87 | { | ||
| 88 | payload.Packaging = this.DefaultPackaging; | ||
| 89 | } | ||
| 90 | else if (payload.Compressed.Value) | ||
| 91 | { | ||
| 92 | payload.Packaging = PackagingType.Embedded; | ||
| 93 | } | ||
| 94 | else | ||
| 95 | { | ||
| 96 | payload.Packaging = PackagingType.External; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | // Embedded payloads that are not assigned a container already are placed in the default attached | ||
| 101 | // container. | ||
| 102 | if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.ContainerRef)) | ||
| 103 | { | ||
| 104 | payload.ContainerRef = BurnConstants.BurnDefaultAttachedContainerName; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs new file mode 100644 index 00000000..854c84e0 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | internal static class BurnBackendErrors | ||
| 8 | { | ||
| 9 | public static Message BAContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) | ||
| 10 | { | ||
| 11 | return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the BA container. When extracting the container at runtime, the file will get overwritten.", payloadId, payloadName); | ||
| 12 | } | ||
| 13 | |||
| 14 | public static Message BAContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) | ||
| 15 | { | ||
| 16 | return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision2, "The location of the payload related to the previous error."); | ||
| 17 | } | ||
| 18 | |||
| 19 | public static Message DuplicateCacheIds(SourceLineNumber originalLineNumber, string cacheId, string packageId) | ||
| 20 | { | ||
| 21 | return Message(originalLineNumber, Ids.DuplicateCacheIds, "The CacheId '{0}' for package '{1}' is duplicated. Each package must have a unique CacheId.", cacheId, packageId); | ||
| 22 | } | ||
| 23 | |||
| 24 | public static Message DuplicateCacheIds2(SourceLineNumber duplicateLineNumber) | ||
| 25 | { | ||
| 26 | return Message(duplicateLineNumber, Ids.DuplicateCacheIds2, "The location of the package related to the previous error."); | ||
| 27 | } | ||
| 28 | |||
| 29 | public static Message ExternalPayloadCollision(SourceLineNumber sourceLineNumbers, string symbolName, string payloadId, string payloadName) | ||
| 30 | { | ||
| 31 | return Message(sourceLineNumbers, Ids.ExternalPayloadCollision, "The external {0} '{1}' has a duplicate Name '{2}'. When building the bundle or laying out the bundle, the file will get overwritten.", symbolName, payloadId, payloadName); | ||
| 32 | } | ||
| 33 | |||
| 34 | public static Message ExternalPayloadCollision2(SourceLineNumber sourceLineNumbers) | ||
| 35 | { | ||
| 36 | return Message(sourceLineNumbers, Ids.ExternalPayloadCollision2, "The location of the symbol related to the previous error."); | ||
| 37 | } | ||
| 38 | |||
| 39 | public static Message MultipleAttachedContainersUnsupported(SourceLineNumber sourceLineNumbers, string containerId) | ||
| 40 | { | ||
| 41 | return Message(sourceLineNumbers, Ids.MultipleAttachedContainersUnsupported, "Bundles don't currently support having more than one attached container. Either remove all authored attached containers to use the default attached container, or make sure all compressed payloads are included in this Container '{0}'.", containerId); | ||
| 42 | } | ||
| 43 | |||
| 44 | public static Message PackageCachePayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName, string packageId) | ||
| 45 | { | ||
| 46 | return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in package '{2}'. When caching the package, the file will get overwritten.", payloadId, payloadName, packageId); | ||
| 47 | } | ||
| 48 | |||
| 49 | public static Message PackageCachePayloadCollision2(SourceLineNumber sourceLineNumbers) | ||
| 50 | { | ||
| 51 | return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision2, "The location of the payload related to the previous error."); | ||
| 52 | } | ||
| 53 | |||
| 54 | private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) | ||
| 55 | { | ||
| 56 | return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); | ||
| 57 | } | ||
| 58 | |||
| 59 | public enum Ids | ||
| 60 | { | ||
| 61 | DuplicateCacheIds = 8000, | ||
| 62 | DuplicateCacheIds2 = 8001, | ||
| 63 | BAContainerPayloadCollision = 8002, | ||
| 64 | BAContainerPayloadCollision2 = 8003, | ||
| 65 | ExternalPayloadCollision = 8004, | ||
| 66 | ExternalPayloadCollision2 = 8005, | ||
| 67 | PackageCachePayloadCollision = 8006, | ||
| 68 | PackageCachePayloadCollision2 = 8007, | ||
| 69 | MultipleAttachedContainersUnsupported = 8008, | ||
| 70 | } // last available is 8499. 8500 is BurnBackendWarnings. | ||
| 71 | } | ||
| 72 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs b/src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs new file mode 100644 index 00000000..03013a08 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset.Extensibility; | ||
| 8 | |||
| 9 | internal class BurnBackendFactory : IBackendFactory | ||
| 10 | { | ||
| 11 | public bool TryCreateBackend(string outputType, string outputFile, out IBackend backend) | ||
| 12 | { | ||
| 13 | if (String.IsNullOrEmpty(outputType)) | ||
| 14 | { | ||
| 15 | outputType = Path.GetExtension(outputFile); | ||
| 16 | } | ||
| 17 | |||
| 18 | switch (outputType.ToLowerInvariant()) | ||
| 19 | { | ||
| 20 | case "bundle": | ||
| 21 | case ".exe": | ||
| 22 | backend = new BundleBackend(); | ||
| 23 | return true; | ||
| 24 | } | ||
| 25 | |||
| 26 | backend = null; | ||
| 27 | return false; | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs b/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs new file mode 100644 index 00000000..a0ffa1dc --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | internal static class BurnBackendWarnings | ||
| 8 | { | ||
| 9 | public static Message AttachedContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) | ||
| 10 | { | ||
| 11 | return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", payloadId, payloadName); | ||
| 12 | } | ||
| 13 | |||
| 14 | public static Message AttachedContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) | ||
| 15 | { | ||
| 16 | return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision2, "The location of the payload related to the previous error."); | ||
| 17 | } | ||
| 18 | |||
| 19 | public static Message EmptyContainer(SourceLineNumber sourceLineNumbers, string containerId) | ||
| 20 | { | ||
| 21 | return Message(sourceLineNumbers, Ids.EmptyContainer, "The Container '{0}' is being ignored because it doesn't have any payloads.", containerId); | ||
| 22 | } | ||
| 23 | |||
| 24 | private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) | ||
| 25 | { | ||
| 26 | return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); | ||
| 27 | } | ||
| 28 | |||
| 29 | public enum Ids | ||
| 30 | { | ||
| 31 | AttachedContainerPayloadCollision = 8500, | ||
| 32 | AttachedContainerPayloadCollision2 = 8501, | ||
| 33 | EmptyContainer = 8502, | ||
| 34 | } // last available is 8999. 9000 is VerboseMessages. | ||
| 35 | } | ||
| 36 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs b/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs new file mode 100644 index 00000000..b34d12c1 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility; | ||
| 7 | |||
| 8 | internal class BurnExtensionFactory : IExtensionFactory | ||
| 9 | { | ||
| 10 | public bool TryCreateExtension(Type extensionType, out object extension) | ||
| 11 | { | ||
| 12 | extension = null; | ||
| 13 | |||
| 14 | if (extensionType == typeof(IBackendFactory)) | ||
| 15 | { | ||
| 16 | extension = new BurnBackendFactory(); | ||
| 17 | } | ||
| 18 | |||
| 19 | return extension != null; | ||
| 20 | } | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs new file mode 100644 index 00000000..e4d2b0c9 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs | |||
| @@ -0,0 +1,214 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Text; | ||
| 9 | using System.Xml; | ||
| 10 | using WixToolset.Core.Burn.Bundles; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 14 | using WixToolset.Extensibility.Data; | ||
| 15 | using WixToolset.Extensibility.Services; | ||
| 16 | |||
| 17 | internal class BurnBackendHelper : IInternalBurnBackendHelper | ||
| 18 | { | ||
| 19 | public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment }; | ||
| 20 | public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment }; | ||
| 21 | |||
| 22 | private readonly IBackendHelper backendHelper; | ||
| 23 | |||
| 24 | private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData(); | ||
| 25 | |||
| 26 | private Dictionary<string, ManifestData> BundleExtensionDataById { get; } = new Dictionary<string, ManifestData>(); | ||
| 27 | |||
| 28 | public BurnBackendHelper(IServiceProvider serviceProvider) | ||
| 29 | { | ||
| 30 | this.backendHelper = serviceProvider.GetService<IBackendHelper>(); | ||
| 31 | } | ||
| 32 | |||
| 33 | #region IBackendHelper interfaces | ||
| 34 | |||
| 35 | public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); | ||
| 36 | |||
| 37 | public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); | ||
| 38 | |||
| 39 | public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); | ||
| 40 | |||
| 41 | public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); | ||
| 42 | |||
| 43 | public string CreateGuid() => this.backendHelper.CreateGuid(); | ||
| 44 | |||
| 45 | public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); | ||
| 46 | |||
| 47 | public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); | ||
| 48 | |||
| 49 | public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); | ||
| 50 | |||
| 51 | public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); | ||
| 52 | |||
| 53 | public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); | ||
| 54 | |||
| 55 | public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); | ||
| 56 | |||
| 57 | public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); | ||
| 58 | |||
| 59 | public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); | ||
| 60 | |||
| 61 | public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); | ||
| 62 | |||
| 63 | public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); | ||
| 64 | |||
| 65 | public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); | ||
| 66 | |||
| 67 | public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); | ||
| 68 | |||
| 69 | public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); | ||
| 70 | |||
| 71 | public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); | ||
| 72 | |||
| 73 | public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); | ||
| 74 | |||
| 75 | #endregion | ||
| 76 | |||
| 77 | #region IBurnBackendHelper interfaces | ||
| 78 | |||
| 79 | public void AddBootstrapperApplicationData(string xml) | ||
| 80 | { | ||
| 81 | this.BootstrapperApplicationManifestData.AddXml(xml); | ||
| 82 | } | ||
| 83 | |||
| 84 | public void AddBootstrapperApplicationData(IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) | ||
| 85 | { | ||
| 86 | this.BootstrapperApplicationManifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BADataNamespace); | ||
| 87 | } | ||
| 88 | |||
| 89 | public void AddBundleExtensionData(string extensionId, string xml) | ||
| 90 | { | ||
| 91 | var manifestData = this.GetBundleExtensionManifestData(extensionId); | ||
| 92 | manifestData.AddXml(xml); | ||
| 93 | } | ||
| 94 | |||
| 95 | public void AddBundleExtensionData(string extensionId, IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) | ||
| 96 | { | ||
| 97 | var manifestData = this.GetBundleExtensionManifestData(extensionId); | ||
| 98 | manifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BundleExtensionDataNamespace); | ||
| 99 | } | ||
| 100 | |||
| 101 | #endregion | ||
| 102 | |||
| 103 | #region IInternalBurnBackendHelper interfaces | ||
| 104 | |||
| 105 | public void WriteBootstrapperApplicationData(XmlWriter writer) | ||
| 106 | { | ||
| 107 | this.BootstrapperApplicationManifestData.Write(writer); | ||
| 108 | } | ||
| 109 | |||
| 110 | public void WriteBundleExtensionData(XmlWriter writer) | ||
| 111 | { | ||
| 112 | foreach (var kvp in this.BundleExtensionDataById) | ||
| 113 | { | ||
| 114 | this.WriteExtension(writer, kvp.Key, kvp.Value); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | #endregion | ||
| 119 | |||
| 120 | private ManifestData GetBundleExtensionManifestData(string extensionId) | ||
| 121 | { | ||
| 122 | if (!this.backendHelper.IsValidIdentifier(extensionId)) | ||
| 123 | { | ||
| 124 | throw new ArgumentException($"'{extensionId}' is not a valid extensionId"); | ||
| 125 | } | ||
| 126 | |||
| 127 | if (!this.BundleExtensionDataById.TryGetValue(extensionId, out var manifestData)) | ||
| 128 | { | ||
| 129 | manifestData = new ManifestData(); | ||
| 130 | this.BundleExtensionDataById.Add(extensionId, manifestData); | ||
| 131 | } | ||
| 132 | |||
| 133 | return manifestData; | ||
| 134 | } | ||
| 135 | |||
| 136 | private void WriteExtension(XmlWriter writer, string extensionId, ManifestData manifestData) | ||
| 137 | { | ||
| 138 | writer.WriteStartElement("BundleExtension"); | ||
| 139 | |||
| 140 | writer.WriteAttributeString("Id", extensionId); | ||
| 141 | |||
| 142 | manifestData.Write(writer); | ||
| 143 | |||
| 144 | writer.WriteEndElement(); | ||
| 145 | } | ||
| 146 | |||
| 147 | private class ManifestData | ||
| 148 | { | ||
| 149 | public ManifestData() | ||
| 150 | { | ||
| 151 | this.Builder = new StringBuilder(); | ||
| 152 | } | ||
| 153 | |||
| 154 | private StringBuilder Builder { get; } | ||
| 155 | |||
| 156 | public void AddSymbol(IntermediateSymbol symbol, bool symbolIdIsIdAttribute, string ns) | ||
| 157 | { | ||
| 158 | // There might be a more efficient way to do this, | ||
| 159 | // but this is an easy way to ensure we're creating valid XML. | ||
| 160 | var sb = new StringBuilder(); | ||
| 161 | using (var writer = XmlWriter.Create(sb, WriterSettings)) | ||
| 162 | { | ||
| 163 | writer.WriteStartElement(symbol.Definition.Name, ns); | ||
| 164 | |||
| 165 | if (symbolIdIsIdAttribute && symbol.Id != null) | ||
| 166 | { | ||
| 167 | writer.WriteAttributeString("Id", symbol.Id.Id); | ||
| 168 | } | ||
| 169 | |||
| 170 | foreach (var field in symbol.Fields) | ||
| 171 | { | ||
| 172 | if (!field.IsNull()) | ||
| 173 | { | ||
| 174 | writer.WriteAttributeString(field.Definition.Name, field.AsString()); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | writer.WriteEndElement(); | ||
| 179 | } | ||
| 180 | |||
| 181 | this.AddXml(sb.ToString()); | ||
| 182 | } | ||
| 183 | |||
| 184 | public void AddXml(string xml) | ||
| 185 | { | ||
| 186 | // There might be a more efficient way to do this, | ||
| 187 | // but this is an easy way to ensure we're given valid XML. | ||
| 188 | var sb = new StringBuilder(); | ||
| 189 | using (var xmlWriter = XmlWriter.Create(sb, WriterSettings)) | ||
| 190 | { | ||
| 191 | AddManifestDataFromString(xmlWriter, xml); | ||
| 192 | } | ||
| 193 | this.Builder.Append(sb.ToString()); | ||
| 194 | } | ||
| 195 | |||
| 196 | public void Write(XmlWriter writer) | ||
| 197 | { | ||
| 198 | AddManifestDataFromString(writer, this.Builder.ToString()); | ||
| 199 | } | ||
| 200 | |||
| 201 | private static void AddManifestDataFromString(XmlWriter xmlWriter, string xml) | ||
| 202 | { | ||
| 203 | using (var stringReader = new StringReader(xml)) | ||
| 204 | using (var xmlReader = XmlReader.Create(stringReader, ReaderSettings)) | ||
| 205 | { | ||
| 206 | while (xmlReader.MoveToContent() != XmlNodeType.None) | ||
| 207 | { | ||
| 208 | xmlWriter.WriteNode(xmlReader, false); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs new file mode 100644 index 00000000..9ef91028 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Core.Burn.Bundles; | ||
| 9 | using WixToolset.Core.Burn.Interfaces; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | |||
| 12 | internal class PayloadHarvester : IPayloadHarvester | ||
| 13 | { | ||
| 14 | private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); | ||
| 15 | |||
| 16 | /// <inheritdoc /> | ||
| 17 | public bool HarvestStandardInformation(WixBundlePayloadSymbol payload) | ||
| 18 | { | ||
| 19 | var filePath = payload.SourceFile?.Path; | ||
| 20 | |||
| 21 | if (String.IsNullOrEmpty(filePath)) | ||
| 22 | { | ||
| 23 | return false; | ||
| 24 | } | ||
| 25 | |||
| 26 | this.UpdatePayloadFileInformation(payload, filePath); | ||
| 27 | |||
| 28 | this.UpdatePayloadVersionInformation(payload, filePath); | ||
| 29 | |||
| 30 | return true; | ||
| 31 | } | ||
| 32 | |||
| 33 | private void UpdatePayloadFileInformation(WixBundlePayloadSymbol payload, string filePath) | ||
| 34 | { | ||
| 35 | var fileInfo = new FileInfo(filePath); | ||
| 36 | |||
| 37 | if (null != fileInfo) | ||
| 38 | { | ||
| 39 | payload.FileSize = fileInfo.Length; | ||
| 40 | |||
| 41 | payload.Hash = BundleHashAlgorithm.Hash(fileInfo); | ||
| 42 | } | ||
| 43 | else | ||
| 44 | { | ||
| 45 | payload.FileSize = 0; | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | private void UpdatePayloadVersionInformation(WixBundlePayloadSymbol payload, string filePath) | ||
| 50 | { | ||
| 51 | var versionInfo = FileVersionInfo.GetVersionInfo(filePath); | ||
| 52 | |||
| 53 | if (null != versionInfo) | ||
| 54 | { | ||
| 55 | // Use the fixed version info block for the file since the resource text may not be a dotted quad. | ||
| 56 | var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); | ||
| 57 | |||
| 58 | if (PayloadHarvester.EmptyVersion != version) | ||
| 59 | { | ||
| 60 | payload.Version = version.ToString(); | ||
| 61 | } | ||
| 62 | |||
| 63 | payload.Description = versionInfo.FileDescription; | ||
| 64 | payload.DisplayName = versionInfo.ProductName; | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs new file mode 100644 index 00000000..59c4f20f --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System.Xml; | ||
| 6 | using WixToolset.Extensibility.Services; | ||
| 7 | |||
| 8 | internal interface IInternalBurnBackendHelper : IBurnBackendHelper | ||
| 9 | { | ||
| 10 | void WriteBootstrapperApplicationData(XmlWriter writer); | ||
| 11 | |||
| 12 | void WriteBundleExtensionData(XmlWriter writer); | ||
| 13 | } | ||
| 14 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/ISearchFacade.cs b/src/wix/WixToolset.Core.Burn/ISearchFacade.cs new file mode 100644 index 00000000..b9ad8649 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/ISearchFacade.cs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System.Xml; | ||
| 6 | |||
| 7 | internal interface ISearchFacade | ||
| 8 | { | ||
| 9 | /// <summary> | ||
| 10 | /// Writes the search to the Burn manifest. | ||
| 11 | /// </summary> | ||
| 12 | /// <param name="writer"></param> | ||
| 13 | void WriteXml(XmlTextWriter writer); | ||
| 14 | } | ||
| 15 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs new file mode 100644 index 00000000..b466d0de --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Inscribe | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixToolset.Core.Burn.Bundles; | ||
| 7 | using WixToolset.Core.Native; | ||
| 8 | using WixToolset.Extensibility.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | internal class InscribeBundleCommand | ||
| 12 | { | ||
| 13 | public InscribeBundleCommand(IInscribeContext context) | ||
| 14 | { | ||
| 15 | this.Context = context; | ||
| 16 | |||
| 17 | this.Messaging = context.ServiceProvider.GetService<IMessaging>(); | ||
| 18 | } | ||
| 19 | |||
| 20 | private IInscribeContext Context { get; } | ||
| 21 | |||
| 22 | public IMessaging Messaging { get; } | ||
| 23 | |||
| 24 | public bool Execute() | ||
| 25 | { | ||
| 26 | var inscribed = false; | ||
| 27 | var tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_signed.exe"); | ||
| 28 | |||
| 29 | using (var reader = BurnReader.Open(this.Context.InputFilePath)) | ||
| 30 | { | ||
| 31 | FileSystem.CopyFile(this.Context.SignedEngineFile, tempFile, allowHardlink: false); | ||
| 32 | |||
| 33 | // If there was an attached container on the original (unsigned) bundle, put it back. | ||
| 34 | if (reader.AttachedContainerSize > 0) | ||
| 35 | { | ||
| 36 | reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin); | ||
| 37 | |||
| 38 | using (var writer = BurnWriter.Open(this.Messaging, tempFile)) | ||
| 39 | { | ||
| 40 | writer.RememberThenResetSignature(); | ||
| 41 | writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); | ||
| 42 | inscribed = true; | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); | ||
| 48 | |||
| 49 | FileSystem.MoveFile(tempFile, this.Context.OutputFile); | ||
| 50 | |||
| 51 | return inscribed; | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs new file mode 100644 index 00000000..a6789796 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs | |||
| @@ -0,0 +1,63 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Inscribe | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset.Core.Burn.Bundles; | ||
| 8 | using WixToolset.Core.Native; | ||
| 9 | using WixToolset.Extensibility.Data; | ||
| 10 | |||
| 11 | internal class InscribeBundleEngineCommand | ||
| 12 | { | ||
| 13 | public InscribeBundleEngineCommand(IInscribeContext context) | ||
| 14 | { | ||
| 15 | this.IntermediateFolder = context.IntermediateFolder; | ||
| 16 | this.InputFilePath = context.InputFilePath; | ||
| 17 | this.OutputFile = context.OutputFile; | ||
| 18 | } | ||
| 19 | |||
| 20 | private string IntermediateFolder { get; } | ||
| 21 | |||
| 22 | private string InputFilePath { get; } | ||
| 23 | |||
| 24 | private string OutputFile { get; } | ||
| 25 | |||
| 26 | public bool Execute() | ||
| 27 | { | ||
| 28 | var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe"); | ||
| 29 | |||
| 30 | using (var reader = BurnReader.Open(this.InputFilePath)) | ||
| 31 | using (var writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) | ||
| 32 | { | ||
| 33 | reader.Stream.Seek(0, SeekOrigin.Begin); | ||
| 34 | |||
| 35 | var buffer = new byte[4 * 1024]; | ||
| 36 | var total = 0; | ||
| 37 | var read = 0; | ||
| 38 | do | ||
| 39 | { | ||
| 40 | read = Math.Min(buffer.Length, (int)reader.EngineSize - total); | ||
| 41 | |||
| 42 | read = reader.Stream.Read(buffer, 0, read); | ||
| 43 | writer.Write(buffer, 0, read); | ||
| 44 | |||
| 45 | total += read; | ||
| 46 | } while (total < reader.EngineSize && 0 < read); | ||
| 47 | |||
| 48 | if (total != reader.EngineSize) | ||
| 49 | { | ||
| 50 | throw new InvalidOperationException("Failed to copy engine out of bundle."); | ||
| 51 | } | ||
| 52 | |||
| 53 | // TODO: update writer with detached container signatures. | ||
| 54 | } | ||
| 55 | |||
| 56 | Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile)); | ||
| 57 | |||
| 58 | FileSystem.MoveFile(tempFile, this.OutputFile); | ||
| 59 | |||
| 60 | return true; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs b/src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs new file mode 100644 index 00000000..1bafa46e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Interfaces | ||
| 4 | { | ||
| 5 | using System.Diagnostics; | ||
| 6 | using WixToolset.Data.Symbols; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Service for harvesting payload information. | ||
| 10 | /// </summary> | ||
| 11 | public interface IPayloadHarvester | ||
| 12 | { | ||
| 13 | /// <summary> | ||
| 14 | /// Uses <see cref="WixBundlePayloadSymbol.SourceFile"/> to: | ||
| 15 | /// update <see cref="WixBundlePayloadSymbol.Hash"/> from file contents, | ||
| 16 | /// update <see cref="WixBundlePayloadSymbol.FileSize"/> from file size, and | ||
| 17 | /// update <see cref="WixBundlePayloadSymbol.Description"/>, <see cref="WixBundlePayloadSymbol.DisplayName"/>, and <see cref="WixBundlePayloadSymbol.Version"/> from <see cref="FileVersionInfo"/>. | ||
| 18 | /// </summary> | ||
| 19 | /// <param name="payload">The symbol to update.</param> | ||
| 20 | /// <returns>Whether the symbol had a source file specified.</returns> | ||
| 21 | bool HarvestStandardInformation(WixBundlePayloadSymbol payload); | ||
| 22 | } | ||
| 23 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/RowIndexedList.cs b/src/wix/WixToolset.Core.Burn/RowIndexedList.cs new file mode 100644 index 00000000..fd762a24 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/RowIndexedList.cs | |||
| @@ -0,0 +1,299 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data.WindowsInstaller; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// A list of rows indexed by their primary key. Unlike a RowDictionary | ||
| 11 | /// this indexed list will track rows in their added order and will allow rows with | ||
| 12 | /// duplicate keys to be added to the list, although only the first row will be indexed. | ||
| 13 | /// </summary> | ||
| 14 | internal sealed class RowIndexedList<T> : IList<T> where T : Row | ||
| 15 | { | ||
| 16 | private readonly Dictionary<string, T> index; | ||
| 17 | private readonly List<T> rows; | ||
| 18 | private readonly List<T> duplicates; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Creates an empty <see cref="RowIndexedList{T}"/>. | ||
| 22 | /// </summary> | ||
| 23 | public RowIndexedList() | ||
| 24 | { | ||
| 25 | this.index = new Dictionary<string, T>(StringComparer.InvariantCulture); | ||
| 26 | this.rows = new List<T>(); | ||
| 27 | this.duplicates = new List<T>(); | ||
| 28 | } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Creates and populates a <see cref="RowIndexedList{T}"/> with the rows from the given enumerator. | ||
| 32 | /// </summary> | ||
| 33 | /// <param name="rows">Rows to index.</param> | ||
| 34 | public RowIndexedList(IEnumerable<T> rows) | ||
| 35 | : this() | ||
| 36 | { | ||
| 37 | foreach (var row in rows) | ||
| 38 | { | ||
| 39 | this.Add(row); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | /// <summary> | ||
| 44 | /// Creates and populates a <see cref="RowIndexedList{T}"/> with the rows from the given <see cref="Table"/>. | ||
| 45 | /// </summary> | ||
| 46 | /// <param name="table">The table to index.</param> | ||
| 47 | /// <remarks> | ||
| 48 | /// Rows added to the index are not automatically added to the given <paramref name="table"/>. | ||
| 49 | /// </remarks> | ||
| 50 | public RowIndexedList(Table table) | ||
| 51 | : this() | ||
| 52 | { | ||
| 53 | if (null != table) | ||
| 54 | { | ||
| 55 | foreach (T row in table.Rows) | ||
| 56 | { | ||
| 57 | this.Add(row); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | /// <summary> | ||
| 63 | /// Gets the duplicates in the list. | ||
| 64 | /// </summary> | ||
| 65 | public IEnumerable<T> Duplicates { get { return this.duplicates; } } | ||
| 66 | |||
| 67 | /// <summary> | ||
| 68 | /// Gets the row by integer key. | ||
| 69 | /// </summary> | ||
| 70 | /// <param name="key">Integer key to look up.</param> | ||
| 71 | /// <returns>Row or null if key is not found.</returns> | ||
| 72 | public T Get(int key) | ||
| 73 | { | ||
| 74 | return this.Get(key.ToString()); | ||
| 75 | } | ||
| 76 | |||
| 77 | /// <summary> | ||
| 78 | /// Gets the row by string key. | ||
| 79 | /// </summary> | ||
| 80 | /// <param name="key">String key to look up.</param> | ||
| 81 | /// <returns>Row or null if key is not found.</returns> | ||
| 82 | public T Get(string key) | ||
| 83 | { | ||
| 84 | return this.TryGet(key, out var result) ? result : null; | ||
| 85 | } | ||
| 86 | |||
| 87 | /// <summary> | ||
| 88 | /// Gets the row by string key if it exists. | ||
| 89 | /// </summary> | ||
| 90 | /// <param name="key">Key of row to get.</param> | ||
| 91 | /// <param name="row">Row found.</param> | ||
| 92 | /// <returns>True if key was found otherwise false.</returns> | ||
| 93 | public bool TryGet(string key, out T row) | ||
| 94 | { | ||
| 95 | return this.index.TryGetValue(key, out row); | ||
| 96 | } | ||
| 97 | |||
| 98 | /// <summary> | ||
| 99 | /// Tries to add a row as long as it would not create a duplicate. | ||
| 100 | /// </summary> | ||
| 101 | /// <param name="row">Row to add.</param> | ||
| 102 | /// <returns>True if the row as added otherwise false.</returns> | ||
| 103 | public bool TryAdd(T row) | ||
| 104 | { | ||
| 105 | try | ||
| 106 | { | ||
| 107 | this.index.Add(row.GetKey(), row); | ||
| 108 | } | ||
| 109 | catch (ArgumentException) // if the key already exists, bail. | ||
| 110 | { | ||
| 111 | return false; | ||
| 112 | } | ||
| 113 | |||
| 114 | this.rows.Add(row); | ||
| 115 | return true; | ||
| 116 | } | ||
| 117 | |||
| 118 | /// <summary> | ||
| 119 | /// Adds a row to the list. If a row with the same key is already index, the row is | ||
| 120 | /// is not in the index but will still be part of the list and added to the duplicates | ||
| 121 | /// list. | ||
| 122 | /// </summary> | ||
| 123 | /// <param name="row"></param> | ||
| 124 | public void Add(T row) | ||
| 125 | { | ||
| 126 | this.rows.Add(row); | ||
| 127 | try | ||
| 128 | { | ||
| 129 | this.index.Add(row.GetKey(), row); | ||
| 130 | } | ||
| 131 | catch (ArgumentException) // if the key already exists, we have a duplicate. | ||
| 132 | { | ||
| 133 | this.duplicates.Add(row); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | /// <summary> | ||
| 138 | /// Gets the index of a row. | ||
| 139 | /// </summary> | ||
| 140 | /// <param name="row">Iterates through the list of rows to find the index of a particular row.</param> | ||
| 141 | /// <returns>Index of row or -1 if not found.</returns> | ||
| 142 | public int IndexOf(T row) | ||
| 143 | { | ||
| 144 | return this.rows.IndexOf(row); | ||
| 145 | } | ||
| 146 | |||
| 147 | /// <summary> | ||
| 148 | /// Inserts a row at a particular index of the list. | ||
| 149 | /// </summary> | ||
| 150 | /// <param name="index">Index to insert the row after.</param> | ||
| 151 | /// <param name="row">Row to insert.</param> | ||
| 152 | public void Insert(int index, T row) | ||
| 153 | { | ||
| 154 | this.rows.Insert(index, row); | ||
| 155 | try | ||
| 156 | { | ||
| 157 | this.index.Add(row.GetKey(), row); | ||
| 158 | } | ||
| 159 | catch (ArgumentException) // if the key already exists, we have a duplicate. | ||
| 160 | { | ||
| 161 | this.duplicates.Add(row); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | /// <summary> | ||
| 166 | /// Removes a row from a particular index. | ||
| 167 | /// </summary> | ||
| 168 | /// <param name="index">Index to remove the row at.</param> | ||
| 169 | public void RemoveAt(int index) | ||
| 170 | { | ||
| 171 | var row = this.rows[index]; | ||
| 172 | |||
| 173 | this.rows.RemoveAt(index); | ||
| 174 | |||
| 175 | if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) | ||
| 176 | { | ||
| 177 | this.index.Remove(row.GetKey()); | ||
| 178 | } | ||
| 179 | else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). | ||
| 180 | { | ||
| 181 | this.duplicates.Remove(row); | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | /// <summary> | ||
| 186 | /// Gets or sets a row at the specified index. | ||
| 187 | /// </summary> | ||
| 188 | /// <param name="index">Index to get the row.</param> | ||
| 189 | /// <returns>Row at specified index.</returns> | ||
| 190 | public T this[int index] | ||
| 191 | { | ||
| 192 | get | ||
| 193 | { | ||
| 194 | return this.rows[index]; | ||
| 195 | } | ||
| 196 | set | ||
| 197 | { | ||
| 198 | this.rows[index] = value; | ||
| 199 | try | ||
| 200 | { | ||
| 201 | this.index.Add(value.GetKey(), value); | ||
| 202 | } | ||
| 203 | catch (ArgumentException) // if the key already exists, we have a duplicate. | ||
| 204 | { | ||
| 205 | this.duplicates.Add(value); | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | /// <summary> | ||
| 211 | /// Empties the list and it's index. | ||
| 212 | /// </summary> | ||
| 213 | public void Clear() | ||
| 214 | { | ||
| 215 | this.index.Clear(); | ||
| 216 | this.rows.Clear(); | ||
| 217 | this.duplicates.Clear(); | ||
| 218 | } | ||
| 219 | |||
| 220 | /// <summary> | ||
| 221 | /// Searches the list for a row without using the index. | ||
| 222 | /// </summary> | ||
| 223 | /// <param name="row">Row to look for in the list.</param> | ||
| 224 | /// <returns>True if the row is in the list, otherwise false.</returns> | ||
| 225 | public bool Contains(T row) | ||
| 226 | { | ||
| 227 | return this.rows.Contains(row); | ||
| 228 | } | ||
| 229 | |||
| 230 | /// <summary> | ||
| 231 | /// Copies the rows of the list to an array. | ||
| 232 | /// </summary> | ||
| 233 | /// <param name="array">Array to copy the list into.</param> | ||
| 234 | /// <param name="arrayIndex">Index to start copying at.</param> | ||
| 235 | public void CopyTo(T[] array, int arrayIndex) | ||
| 236 | { | ||
| 237 | this.rows.CopyTo(array, arrayIndex); | ||
| 238 | } | ||
| 239 | |||
| 240 | /// <summary> | ||
| 241 | /// Number of rows in the list. | ||
| 242 | /// </summary> | ||
| 243 | public int Count | ||
| 244 | { | ||
| 245 | get { return this.rows.Count; } | ||
| 246 | } | ||
| 247 | |||
| 248 | /// <summary> | ||
| 249 | /// Indicates whether the list is read-only. Always false. | ||
| 250 | /// </summary> | ||
| 251 | public bool IsReadOnly | ||
| 252 | { | ||
| 253 | get { return false; } | ||
| 254 | } | ||
| 255 | |||
| 256 | /// <summary> | ||
| 257 | /// Removes a row from the list. Indexed rows will be removed but the colleciton will NOT | ||
| 258 | /// promote duplicates to the index automatically. The duplicate would also need to be removed | ||
| 259 | /// and re-added to be indexed. | ||
| 260 | /// </summary> | ||
| 261 | /// <param name="row"></param> | ||
| 262 | /// <returns></returns> | ||
| 263 | public bool Remove(T row) | ||
| 264 | { | ||
| 265 | var removed = this.rows.Remove(row); | ||
| 266 | if (removed) | ||
| 267 | { | ||
| 268 | if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) | ||
| 269 | { | ||
| 270 | this.index.Remove(row.GetKey()); | ||
| 271 | } | ||
| 272 | else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). | ||
| 273 | { | ||
| 274 | this.duplicates.Remove(row); | ||
| 275 | } | ||
| 276 | } | ||
| 277 | |||
| 278 | return removed; | ||
| 279 | } | ||
| 280 | |||
| 281 | /// <summary> | ||
| 282 | /// Gets an enumerator over the whole list. | ||
| 283 | /// </summary> | ||
| 284 | /// <returns>List enumerator.</returns> | ||
| 285 | public IEnumerator<T> GetEnumerator() | ||
| 286 | { | ||
| 287 | return this.rows.GetEnumerator(); | ||
| 288 | } | ||
| 289 | |||
| 290 | /// <summary> | ||
| 291 | /// Gets an untyped enumerator over the whole list. | ||
| 292 | /// </summary> | ||
| 293 | /// <returns>Untyped list enumerator.</returns> | ||
| 294 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() | ||
| 295 | { | ||
| 296 | return this.rows.GetEnumerator(); | ||
| 297 | } | ||
| 298 | } | ||
| 299 | } | ||
diff --git a/src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj new file mode 100644 index 00000000..f2da8a50 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFrameworks>netstandard2.0</TargetFrameworks> | ||
| 7 | <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks> | ||
| 8 | <Description>Core Burn</Description> | ||
| 9 | <Title>WiX Toolset Core Burn</Title> | ||
| 10 | <DebugType>embedded</DebugType> | ||
| 11 | <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
| 12 | <CreateDocumentationFile>true</CreateDocumentationFile> | ||
| 13 | </PropertyGroup> | ||
| 14 | |||
| 15 | <ItemGroup> | ||
| 16 | <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> | ||
| 17 | <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1> | ||
| 18 | </AssemblyAttribute> | ||
| 19 | <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> | ||
| 20 | <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1> | ||
| 21 | </AssemblyAttribute> | ||
| 22 | <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> | ||
| 23 | <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1> | ||
| 24 | </AssemblyAttribute> | ||
| 25 | </ItemGroup> | ||
| 26 | |||
| 27 | <ItemGroup> | ||
| 28 | <PackageReference Include="WixToolset.Burn" Version="4.0.*" /> | ||
| 29 | <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" /> | ||
| 30 | <PackageReference Include="WixToolset.Data" Version="4.0.*" /> | ||
| 31 | <PackageReference Include="WixToolset.Dtf.Resources" Version="4.0.*" /> | ||
| 32 | <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" /> | ||
| 33 | </ItemGroup> | ||
| 34 | |||
| 35 | <ItemGroup> | ||
| 36 | <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | ||
| 37 | <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="all" /> | ||
| 38 | </ItemGroup> | ||
| 39 | </Project> | ||
diff --git a/src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs new file mode 100644 index 00000000..58076d5e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Core.Burn.ExtensibilityServices; | ||
| 8 | using WixToolset.Core.Burn.Interfaces; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | /// <summary> | ||
| 12 | /// Extensions methods for adding Burn services. | ||
| 13 | /// </summary> | ||
| 14 | public static class WixToolsetCoreServiceProviderExtensions | ||
| 15 | { | ||
| 16 | /// <summary> | ||
| 17 | /// Adds Burn Services. | ||
| 18 | /// </summary> | ||
| 19 | /// <param name="coreProvider"></param> | ||
| 20 | /// <returns></returns> | ||
| 21 | public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider) | ||
| 22 | { | ||
| 23 | AddServices(coreProvider); | ||
| 24 | |||
| 25 | var extensionManager = coreProvider.GetService<IExtensionManager>(); | ||
| 26 | extensionManager.Add(typeof(BurnExtensionFactory).Assembly); | ||
| 27 | |||
| 28 | return coreProvider; | ||
| 29 | } | ||
| 30 | |||
| 31 | private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) | ||
| 32 | { | ||
| 33 | // Singletons. | ||
| 34 | coreProvider.AddService((provider, singletons) => AddSingleton<IInternalBurnBackendHelper>(singletons, new BurnBackendHelper(provider))); | ||
| 35 | coreProvider.AddService((provider, singletons) => AddSingleton<IPayloadHarvester>(singletons, new PayloadHarvester())); | ||
| 36 | coreProvider.AddService((provider, singletons) => AddSingleton<IBurnBackendHelper>(singletons, provider.GetService<IInternalBurnBackendHelper>())); | ||
| 37 | } | ||
| 38 | |||
| 39 | private static T AddSingleton<T>(Dictionary<Type, object> singletons, T service) where T : class | ||
| 40 | { | ||
| 41 | singletons.Add(typeof(T), service); | ||
| 42 | return service; | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
diff --git a/src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs b/src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs new file mode 100644 index 00000000..5567541c --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensionCache | ||
| 4 | { | ||
| 5 | internal class CachedExtension | ||
| 6 | { | ||
| 7 | public CachedExtension(string id, string version, bool damaged) | ||
| 8 | { | ||
| 9 | this.Id = id; | ||
| 10 | this.Version = version; | ||
| 11 | this.Damaged = damaged; | ||
| 12 | } | ||
| 13 | |||
| 14 | public string Id { get; } | ||
| 15 | |||
| 16 | public string Version { get; } | ||
| 17 | |||
| 18 | public bool Damaged { get; } | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs new file mode 100644 index 00000000..256eeb0b --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs | |||
| @@ -0,0 +1,248 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensionCache | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Threading; | ||
| 10 | using System.Threading.Tasks; | ||
| 11 | using NuGet.Common; | ||
| 12 | using NuGet.Configuration; | ||
| 13 | using NuGet.Credentials; | ||
| 14 | using NuGet.Packaging; | ||
| 15 | using NuGet.Protocol; | ||
| 16 | using NuGet.Protocol.Core.Types; | ||
| 17 | using NuGet.Versioning; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Extension cache manager. | ||
| 21 | /// </summary> | ||
| 22 | internal class ExtensionCacheManager | ||
| 23 | { | ||
| 24 | public string CacheFolder(bool global) => global ? this.GlobalCacheFolder() : this.LocalCacheFolder(); | ||
| 25 | |||
| 26 | public string LocalCacheFolder() => Path.Combine(Environment.CurrentDirectory, ".wix", "extensions"); | ||
| 27 | |||
| 28 | public string GlobalCacheFolder() | ||
| 29 | { | ||
| 30 | var baseFolder = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); | ||
| 31 | return Path.Combine(baseFolder, ".wix", "extensions"); | ||
| 32 | } | ||
| 33 | |||
| 34 | public async Task<bool> AddAsync(bool global, string extension, CancellationToken cancellationToken) | ||
| 35 | { | ||
| 36 | if (String.IsNullOrEmpty(extension)) | ||
| 37 | { | ||
| 38 | throw new ArgumentNullException(nameof(extension)); | ||
| 39 | } | ||
| 40 | |||
| 41 | (var extensionId, var extensionVersion) = ParseExtensionReference(extension); | ||
| 42 | |||
| 43 | var result = await this.DownloadAndExtractAsync(global, extensionId, extensionVersion, cancellationToken); | ||
| 44 | |||
| 45 | return result; | ||
| 46 | } | ||
| 47 | |||
| 48 | public Task<bool> RemoveAsync(bool global, string extension, CancellationToken cancellationToken) | ||
| 49 | { | ||
| 50 | if (String.IsNullOrEmpty(extension)) | ||
| 51 | { | ||
| 52 | throw new ArgumentNullException(nameof(extension)); | ||
| 53 | } | ||
| 54 | |||
| 55 | (var extensionId, var extensionVersion) = ParseExtensionReference(extension); | ||
| 56 | |||
| 57 | var cacheFolder = this.CacheFolder(global); | ||
| 58 | |||
| 59 | cacheFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); | ||
| 60 | |||
| 61 | if (Directory.Exists(cacheFolder)) | ||
| 62 | { | ||
| 63 | cancellationToken.ThrowIfCancellationRequested(); | ||
| 64 | |||
| 65 | Directory.Delete(cacheFolder, true); | ||
| 66 | return Task.FromResult(true); | ||
| 67 | } | ||
| 68 | |||
| 69 | return Task.FromResult(false); | ||
| 70 | } | ||
| 71 | |||
| 72 | public Task<IEnumerable<CachedExtension>> ListAsync(bool global, string extension, CancellationToken cancellationToken) | ||
| 73 | { | ||
| 74 | var found = new List<CachedExtension>(); | ||
| 75 | |||
| 76 | (var extensionId, var extensionVersion) = ParseExtensionReference(extension); | ||
| 77 | |||
| 78 | var cacheFolder = this.CacheFolder(global); | ||
| 79 | |||
| 80 | var searchFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); | ||
| 81 | |||
| 82 | if (!Directory.Exists(searchFolder)) | ||
| 83 | { | ||
| 84 | } | ||
| 85 | else if (!String.IsNullOrEmpty(extensionVersion)) // looking for an explicit version of an extension. | ||
| 86 | { | ||
| 87 | var present = ExtensionFileExists(cacheFolder, extensionId, extensionVersion); | ||
| 88 | found.Add(new CachedExtension(extensionId, extensionVersion, !present)); | ||
| 89 | } | ||
| 90 | else // looking for all versions of an extension or all versions of all extensions. | ||
| 91 | { | ||
| 92 | IEnumerable<string> foundExtensionIds; | ||
| 93 | |||
| 94 | if (String.IsNullOrEmpty(extensionId)) | ||
| 95 | { | ||
| 96 | // Looking for all versions of all extensions. | ||
| 97 | foundExtensionIds = Directory.GetDirectories(cacheFolder).Select(folder => Path.GetFileName(folder)).ToList(); | ||
| 98 | } | ||
| 99 | else | ||
| 100 | { | ||
| 101 | // Looking for all versions of a single extension. | ||
| 102 | var extensionFolder = Path.Combine(cacheFolder, extensionId); | ||
| 103 | foundExtensionIds = Directory.Exists(extensionFolder) ? new[] { extensionId } : Array.Empty<string>(); | ||
| 104 | } | ||
| 105 | |||
| 106 | foreach (var foundExtensionId in foundExtensionIds) | ||
| 107 | { | ||
| 108 | var extensionFolder = Path.Combine(cacheFolder, foundExtensionId); | ||
| 109 | |||
| 110 | foreach (var folder in Directory.GetDirectories(extensionFolder)) | ||
| 111 | { | ||
| 112 | cancellationToken.ThrowIfCancellationRequested(); | ||
| 113 | |||
| 114 | var foundExtensionVersion = Path.GetFileName(folder); | ||
| 115 | |||
| 116 | if (!NuGetVersion.TryParse(foundExtensionVersion, out _)) | ||
| 117 | { | ||
| 118 | continue; | ||
| 119 | } | ||
| 120 | |||
| 121 | var present = ExtensionFileExists(cacheFolder, foundExtensionId, foundExtensionVersion); | ||
| 122 | found.Add(new CachedExtension(foundExtensionId, foundExtensionVersion, !present)); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | return Task.FromResult((IEnumerable<CachedExtension>)found); | ||
| 128 | } | ||
| 129 | |||
| 130 | private async Task<bool> DownloadAndExtractAsync(bool global, string id, string version, CancellationToken cancellationToken) | ||
| 131 | { | ||
| 132 | var logger = NullLogger.Instance; | ||
| 133 | |||
| 134 | DefaultCredentialServiceUtility.SetupDefaultCredentialService(logger, nonInteractive: false); | ||
| 135 | |||
| 136 | var settings = Settings.LoadDefaultSettings(root: Environment.CurrentDirectory); | ||
| 137 | var sources = PackageSourceProvider.LoadPackageSources(settings).Where(s => s.IsEnabled); | ||
| 138 | |||
| 139 | using (var cache = new SourceCacheContext()) | ||
| 140 | { | ||
| 141 | PackageSource versionSource = null; | ||
| 142 | |||
| 143 | var nugetVersion = String.IsNullOrEmpty(version) ? null : new NuGetVersion(version); | ||
| 144 | |||
| 145 | if (nugetVersion is null) | ||
| 146 | { | ||
| 147 | foreach (var source in sources) | ||
| 148 | { | ||
| 149 | var repository = Repository.Factory.GetCoreV3(source.Source); | ||
| 150 | var resource = await repository.GetResourceAsync<FindPackageByIdResource>(); | ||
| 151 | |||
| 152 | var availableVersions = await resource.GetAllVersionsAsync(id, cache, logger, cancellationToken); | ||
| 153 | foreach (var availableVersion in availableVersions) | ||
| 154 | { | ||
| 155 | if (nugetVersion is null || nugetVersion < availableVersion) | ||
| 156 | { | ||
| 157 | nugetVersion = availableVersion; | ||
| 158 | versionSource = source; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | if (nugetVersion is null) | ||
| 164 | { | ||
| 165 | return false; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | var searchSources = versionSource is null ? sources : new[] { versionSource }; | ||
| 170 | |||
| 171 | var extensionFolder = Path.Combine(this.CacheFolder(global), id, nugetVersion.ToString()); | ||
| 172 | |||
| 173 | foreach (var source in searchSources) | ||
| 174 | { | ||
| 175 | var repository = Repository.Factory.GetCoreV3(source.Source); | ||
| 176 | var resource = await repository.GetResourceAsync<FindPackageByIdResource>(); | ||
| 177 | |||
| 178 | using (var stream = new MemoryStream()) | ||
| 179 | { | ||
| 180 | var downloaded = await resource.CopyNupkgToStreamAsync(id, nugetVersion, stream, cache, logger, cancellationToken); | ||
| 181 | |||
| 182 | if (downloaded) | ||
| 183 | { | ||
| 184 | stream.Position = 0; | ||
| 185 | |||
| 186 | using (var archive = new PackageArchiveReader(stream)) | ||
| 187 | { | ||
| 188 | var files = PackagingConstants.Folders.Known.SelectMany(folder => archive.GetFiles(folder)).Distinct(StringComparer.OrdinalIgnoreCase); | ||
| 189 | await archive.CopyFilesAsync(extensionFolder, files, this.ExtractProgress, logger, cancellationToken); | ||
| 190 | } | ||
| 191 | |||
| 192 | return true; | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | return false; | ||
| 199 | } | ||
| 200 | |||
| 201 | private string ExtractProgress(string sourceFile, string targetPath, Stream fileStream) => fileStream.CopyToFile(targetPath); | ||
| 202 | |||
| 203 | private static (string extensionId, string extensionVersion) ParseExtensionReference(string extensionReference) | ||
| 204 | { | ||
| 205 | var extensionId = extensionReference ?? String.Empty; | ||
| 206 | var extensionVersion = String.Empty; | ||
| 207 | |||
| 208 | var index = extensionId.LastIndexOf('/'); | ||
| 209 | if (index > 0) | ||
| 210 | { | ||
| 211 | extensionVersion = extensionReference.Substring(index + 1); | ||
| 212 | extensionId = extensionReference.Substring(0, index); | ||
| 213 | |||
| 214 | if (!NuGetVersion.TryParse(extensionVersion, out _)) | ||
| 215 | { | ||
| 216 | throw new ArgumentException($"Invalid extension version in {extensionReference}"); | ||
| 217 | } | ||
| 218 | |||
| 219 | if (String.IsNullOrEmpty(extensionId)) | ||
| 220 | { | ||
| 221 | throw new ArgumentException($"Invalid extension id in {extensionReference}"); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | return (extensionId, extensionVersion); | ||
| 226 | } | ||
| 227 | |||
| 228 | private static bool ExtensionFileExists(string baseFolder, string extensionId, string extensionVersion) | ||
| 229 | { | ||
| 230 | var toolsFolder = Path.Combine(baseFolder, extensionId, extensionVersion, "tools"); | ||
| 231 | if (!Directory.Exists(toolsFolder)) | ||
| 232 | { | ||
| 233 | return false; | ||
| 234 | } | ||
| 235 | |||
| 236 | var extensionAssembly = Path.Combine(toolsFolder, extensionId + ".dll"); | ||
| 237 | |||
| 238 | var present = File.Exists(extensionAssembly); | ||
| 239 | if (!present) | ||
| 240 | { | ||
| 241 | extensionAssembly = Path.Combine(toolsFolder, extensionId + ".exe"); | ||
| 242 | present = File.Exists(extensionAssembly); | ||
| 243 | } | ||
| 244 | |||
| 245 | return present; | ||
| 246 | } | ||
| 247 | } | ||
| 248 | } | ||
diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs new file mode 100644 index 00000000..94ee4f22 --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs | |||
| @@ -0,0 +1,181 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensionCache | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using System.Threading; | ||
| 9 | using System.Threading.Tasks; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// Extension cache manager command. | ||
| 15 | /// </summary> | ||
| 16 | internal class ExtensionCacheManagerCommand : ICommandLineCommand | ||
| 17 | { | ||
| 18 | private enum CacheSubcommand | ||
| 19 | { | ||
| 20 | Add, | ||
| 21 | Remove, | ||
| 22 | List | ||
| 23 | } | ||
| 24 | |||
| 25 | public ExtensionCacheManagerCommand(IServiceProvider serviceProvider) | ||
| 26 | { | ||
| 27 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 28 | this.ExtensionReferences = new List<string>(); | ||
| 29 | } | ||
| 30 | |||
| 31 | private IMessaging Messaging { get; } | ||
| 32 | |||
| 33 | public bool ShowLogo { get; private set; } | ||
| 34 | |||
| 35 | public bool StopParsing { get; private set; } | ||
| 36 | |||
| 37 | private bool ShowHelp { get; set; } | ||
| 38 | |||
| 39 | private bool Global { get; set; } | ||
| 40 | |||
| 41 | private CacheSubcommand? Subcommand { get; set; } | ||
| 42 | |||
| 43 | private List<string> ExtensionReferences { get; } | ||
| 44 | |||
| 45 | public async Task<int> ExecuteAsync(CancellationToken cancellationToken) | ||
| 46 | { | ||
| 47 | if (this.ShowHelp || !this.Subcommand.HasValue) | ||
| 48 | { | ||
| 49 | DisplayHelp(); | ||
| 50 | return 1; | ||
| 51 | } | ||
| 52 | |||
| 53 | var success = false; | ||
| 54 | var cacheManager = new ExtensionCacheManager(); | ||
| 55 | |||
| 56 | switch (this.Subcommand) | ||
| 57 | { | ||
| 58 | case CacheSubcommand.Add: | ||
| 59 | success = await this.AddExtensions(cacheManager, cancellationToken); | ||
| 60 | break; | ||
| 61 | |||
| 62 | case CacheSubcommand.Remove: | ||
| 63 | success = await this.RemoveExtensions(cacheManager, cancellationToken); | ||
| 64 | break; | ||
| 65 | |||
| 66 | case CacheSubcommand.List: | ||
| 67 | success = await this.ListExtensions(cacheManager, cancellationToken); | ||
| 68 | break; | ||
| 69 | } | ||
| 70 | |||
| 71 | return success ? 0 : 2; | ||
| 72 | } | ||
| 73 | |||
| 74 | public bool TryParseArgument(ICommandLineParser parser, string argument) | ||
| 75 | { | ||
| 76 | if (!parser.IsSwitch(argument)) | ||
| 77 | { | ||
| 78 | if (!this.Subcommand.HasValue) | ||
| 79 | { | ||
| 80 | if (!Enum.TryParse(argument, true, out CacheSubcommand subcommand)) | ||
| 81 | { | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | |||
| 85 | this.Subcommand = subcommand; | ||
| 86 | } | ||
| 87 | else | ||
| 88 | { | ||
| 89 | this.ExtensionReferences.Add(argument); | ||
| 90 | } | ||
| 91 | |||
| 92 | return true; | ||
| 93 | } | ||
| 94 | |||
| 95 | var parameter = argument.Substring(1); | ||
| 96 | switch (parameter.ToLowerInvariant()) | ||
| 97 | { | ||
| 98 | case "?": | ||
| 99 | case "h": | ||
| 100 | case "-help": | ||
| 101 | this.ShowHelp = true; | ||
| 102 | this.ShowLogo = true; | ||
| 103 | this.StopParsing = true; | ||
| 104 | return true; | ||
| 105 | |||
| 106 | case "nologo": | ||
| 107 | case "-nologo": | ||
| 108 | this.ShowLogo = false; | ||
| 109 | return true; | ||
| 110 | |||
| 111 | case "g": | ||
| 112 | case "-global": | ||
| 113 | this.Global = true; | ||
| 114 | return true; | ||
| 115 | } | ||
| 116 | |||
| 117 | return false; | ||
| 118 | } | ||
| 119 | |||
| 120 | private async Task<bool> AddExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) | ||
| 121 | { | ||
| 122 | var success = false; | ||
| 123 | |||
| 124 | foreach (var extensionRef in this.ExtensionReferences) | ||
| 125 | { | ||
| 126 | var added = await cacheManager.AddAsync(this.Global, extensionRef, cancellationToken); | ||
| 127 | success |= added; | ||
| 128 | } | ||
| 129 | |||
| 130 | return success; | ||
| 131 | } | ||
| 132 | |||
| 133 | private async Task<bool> RemoveExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) | ||
| 134 | { | ||
| 135 | var success = false; | ||
| 136 | |||
| 137 | foreach (var extensionRef in this.ExtensionReferences) | ||
| 138 | { | ||
| 139 | var removed = await cacheManager.RemoveAsync(this.Global, extensionRef, cancellationToken); | ||
| 140 | success |= removed; | ||
| 141 | } | ||
| 142 | |||
| 143 | return success; | ||
| 144 | } | ||
| 145 | |||
| 146 | private async Task<bool> ListExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) | ||
| 147 | { | ||
| 148 | var found = false; | ||
| 149 | var extensionRef = this.ExtensionReferences.FirstOrDefault(); | ||
| 150 | |||
| 151 | var extensions = await cacheManager.ListAsync(this.Global, extensionRef, cancellationToken); | ||
| 152 | |||
| 153 | foreach (var extension in extensions) | ||
| 154 | { | ||
| 155 | this.Messaging.Write($"{extension.Id} {extension.Version}{(extension.Damaged ? " (damaged)" : String.Empty)}"); | ||
| 156 | found = true; | ||
| 157 | } | ||
| 158 | |||
| 159 | return found; | ||
| 160 | } | ||
| 161 | |||
| 162 | private static void DisplayHelp() | ||
| 163 | { | ||
| 164 | Console.WriteLine(); | ||
| 165 | Console.WriteLine("Usage: wix extension add|remove|list [extensionRef]"); | ||
| 166 | Console.WriteLine(); | ||
| 167 | Console.WriteLine("Options:"); | ||
| 168 | Console.WriteLine(" -h|--help Show command line help."); | ||
| 169 | Console.WriteLine(" -g|--global Add/remove the extension for the current user."); | ||
| 170 | Console.WriteLine(" --nologo Suppress displaying the logo information."); | ||
| 171 | Console.WriteLine(); | ||
| 172 | Console.WriteLine("Commands:"); | ||
| 173 | Console.WriteLine(); | ||
| 174 | Console.WriteLine(" add Add extension to the cache."); | ||
| 175 | Console.WriteLine(" list List extensions in the cache."); | ||
| 176 | Console.WriteLine(" remove Remove extension from the cache."); | ||
| 177 | Console.WriteLine(); | ||
| 178 | Console.WriteLine(" extensionRef format: extensionId/version (the version is optional)"); | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs new file mode 100644 index 00000000..2a603adf --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensionCache | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Extensibility; | ||
| 8 | using WixToolset.Extensibility.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | /// <summary> | ||
| 12 | /// Parses the "extension" command-line command. See <c>ExtensionCacheManagerCommand</c> | ||
| 13 | /// for the bulk of the command-line processing. | ||
| 14 | /// </summary> | ||
| 15 | internal class ExtensionCacheManagerExtensionCommandLine : BaseExtensionCommandLine | ||
| 16 | { | ||
| 17 | public ExtensionCacheManagerExtensionCommandLine(IServiceProvider serviceProvider) | ||
| 18 | { | ||
| 19 | this.ServiceProvider = serviceProvider; | ||
| 20 | } | ||
| 21 | |||
| 22 | private IServiceProvider ServiceProvider { get; } | ||
| 23 | |||
| 24 | public override IReadOnlyCollection<ExtensionCommandLineSwitch> CommandLineSwitches => new ExtensionCommandLineSwitch[] | ||
| 25 | { | ||
| 26 | new ExtensionCommandLineSwitch { Switch = "extension", Description = "Manage extension cache." }, | ||
| 27 | }; | ||
| 28 | |||
| 29 | public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) | ||
| 30 | { | ||
| 31 | command = null; | ||
| 32 | |||
| 33 | if ("extension".Equals(argument, StringComparison.OrdinalIgnoreCase)) | ||
| 34 | { | ||
| 35 | command = new ExtensionCacheManagerCommand(this.ServiceProvider); | ||
| 36 | } | ||
| 37 | |||
| 38 | return command != null; | ||
| 39 | } | ||
| 40 | } | ||
| 41 | } | ||
diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs new file mode 100644 index 00000000..c38e5c70 --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensionCache | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility; | ||
| 7 | using WixToolset.Extensibility.Services; | ||
| 8 | |||
| 9 | internal class ExtensionCacheManagerExtensionFactory : IExtensionFactory | ||
| 10 | { | ||
| 11 | public ExtensionCacheManagerExtensionFactory(IServiceProvider serviceProvider) | ||
| 12 | { | ||
| 13 | this.ServiceProvider = serviceProvider; | ||
| 14 | } | ||
| 15 | |||
| 16 | private IServiceProvider ServiceProvider { get; } | ||
| 17 | |||
| 18 | public bool TryCreateExtension(Type extensionType, out object extension) | ||
| 19 | { | ||
| 20 | extension = null; | ||
| 21 | |||
| 22 | if (extensionType == typeof(IExtensionCommandLine)) | ||
| 23 | { | ||
| 24 | extension = new ExtensionCacheManagerExtensionCommandLine(this.ServiceProvider); | ||
| 25 | } | ||
| 26 | |||
| 27 | return extension != null; | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj b/src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj new file mode 100644 index 00000000..1383305c --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFrameworks>netstandard2.0</TargetFrameworks> | ||
| 7 | <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks> | ||
| 8 | <Description>Extension Cache</Description> | ||
| 9 | <Title>WiX Toolset Extension Cache</Title> | ||
| 10 | <DebugType>embedded</DebugType> | ||
| 11 | <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
| 12 | <CreateDocumentationFile>true</CreateDocumentationFile> | ||
| 13 | </PropertyGroup> | ||
| 14 | |||
| 15 | <ItemGroup> | ||
| 16 | <PackageReference Include="WixToolset.Data" Version="4.0.*" /> | ||
| 17 | <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" /> | ||
| 18 | </ItemGroup> | ||
| 19 | |||
| 20 | <ItemGroup> | ||
| 21 | <PackageReference Include="NuGet.Credentials" Version="5.6.0" /> | ||
| 22 | <PackageReference Include="NuGet.Protocol" Version="5.6.0" /> | ||
| 23 | </ItemGroup> | ||
| 24 | |||
| 25 | <ItemGroup> | ||
| 26 | <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | ||
| 27 | <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" /> | ||
| 28 | </ItemGroup> | ||
| 29 | </Project> | ||
diff --git a/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs new file mode 100644 index 00000000..424fc469 --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensionCache | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Extensibility.Services; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Extensions methods for adding ExtensionCache services. | ||
| 11 | /// </summary> | ||
| 12 | public static class WixToolsetCoreServiceProviderExtensions | ||
| 13 | { | ||
| 14 | /// <summary> | ||
| 15 | /// Adds ExtensionCache services. | ||
| 16 | /// </summary> | ||
| 17 | /// <param name="coreProvider"></param> | ||
| 18 | /// <returns></returns> | ||
| 19 | public static IWixToolsetCoreServiceProvider AddExtensionCacheManager(this IWixToolsetCoreServiceProvider coreProvider) | ||
| 20 | { | ||
| 21 | var extensionManager = coreProvider.GetService<IExtensionManager>(); | ||
| 22 | extensionManager.Add(typeof(ExtensionCacheManagerExtensionFactory).Assembly); | ||
| 23 | |||
| 24 | coreProvider.AddService(CreateExtensionCacheManager); | ||
| 25 | return coreProvider; | ||
| 26 | } | ||
| 27 | |||
| 28 | private static ExtensionCacheManager CreateExtensionCacheManager(IWixToolsetCoreServiceProvider coreProvider, Dictionary<Type, object> singletons) | ||
| 29 | { | ||
| 30 | var extensionCacheManager = new ExtensionCacheManager(); | ||
| 31 | singletons.Add(typeof(ExtensionCacheManager), extensionCacheManager); | ||
| 32 | |||
| 33 | return extensionCacheManager; | ||
| 34 | } | ||
| 35 | } | ||
| 36 | } | ||
diff --git a/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs new file mode 100644 index 00000000..8c9f31e6 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs | |||
| @@ -0,0 +1,139 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.TestPackage | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Xml; | ||
| 7 | using WixToolset.Core.Burn.Bundles; | ||
| 8 | using WixToolset.Extensibility.Services; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Class to extract bundle contents for testing. | ||
| 12 | /// </summary> | ||
| 13 | public class BundleExtractor | ||
| 14 | { | ||
| 15 | /// <summary> | ||
| 16 | /// Extracts the BA container. | ||
| 17 | /// </summary> | ||
| 18 | /// <param name="messaging"></param> | ||
| 19 | /// <param name="bundleFilePath">Path to the bundle.</param> | ||
| 20 | /// <param name="destinationFolderPath">Path to extract to.</param> | ||
| 21 | /// <param name="tempFolderPath">Temp path for extraction.</param> | ||
| 22 | /// <returns></returns> | ||
| 23 | public static ExtractBAContainerResult ExtractBAContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) | ||
| 24 | { | ||
| 25 | var result = new ExtractBAContainerResult(); | ||
| 26 | Directory.CreateDirectory(tempFolderPath); | ||
| 27 | using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) | ||
| 28 | { | ||
| 29 | result.Success = burnReader.ExtractUXContainer(destinationFolderPath, tempFolderPath); | ||
| 30 | } | ||
| 31 | |||
| 32 | if (result.Success) | ||
| 33 | { | ||
| 34 | result.ManifestDocument = LoadBurnManifest(destinationFolderPath); | ||
| 35 | result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); | ||
| 36 | |||
| 37 | result.BADataDocument = LoadBAData(destinationFolderPath); | ||
| 38 | result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba"); | ||
| 39 | |||
| 40 | result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath); | ||
| 41 | result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be"); | ||
| 42 | } | ||
| 43 | |||
| 44 | return result; | ||
| 45 | } | ||
| 46 | |||
| 47 | /// <summary> | ||
| 48 | /// Extracts the attached container. | ||
| 49 | /// </summary> | ||
| 50 | /// <param name="messaging"></param> | ||
| 51 | /// <param name="bundleFilePath">Path to the bundle.</param> | ||
| 52 | /// <param name="destinationFolderPath">Path to extract to.</param> | ||
| 53 | /// <param name="tempFolderPath">Temp path for extraction.</param> | ||
| 54 | /// <returns>True if there was an attached container.</returns> | ||
| 55 | public static bool ExtractAttachedContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) | ||
| 56 | { | ||
| 57 | Directory.CreateDirectory(tempFolderPath); | ||
| 58 | using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) | ||
| 59 | { | ||
| 60 | return burnReader.ExtractAttachedContainer(destinationFolderPath, tempFolderPath); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | /// <summary> | ||
| 65 | /// Gets an <see cref="XmlNamespaceManager"/> for BootstrapperApplicationData.xml with the given prefix assigned to the root namespace. | ||
| 66 | /// </summary> | ||
| 67 | /// <param name="document"></param> | ||
| 68 | /// <param name="prefix"></param> | ||
| 69 | /// <returns></returns> | ||
| 70 | public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix) | ||
| 71 | { | ||
| 72 | var namespaceManager = new XmlNamespaceManager(document.NameTable); | ||
| 73 | namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace); | ||
| 74 | return namespaceManager; | ||
| 75 | } | ||
| 76 | |||
| 77 | /// <summary> | ||
| 78 | /// Gets an <see cref="XmlNamespaceManager"/> for BundleExtensionData.xml with the given prefix assigned to the root namespace. | ||
| 79 | /// </summary> | ||
| 80 | /// <param name="document"></param> | ||
| 81 | /// <param name="prefix"></param> | ||
| 82 | /// <returns></returns> | ||
| 83 | public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix) | ||
| 84 | { | ||
| 85 | var namespaceManager = new XmlNamespaceManager(document.NameTable); | ||
| 86 | namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace); | ||
| 87 | return namespaceManager; | ||
| 88 | } | ||
| 89 | |||
| 90 | /// <summary> | ||
| 91 | /// Gets an <see cref="XmlNamespaceManager"/> for the Burn manifest.xml with the given prefix assigned to the root namespace. | ||
| 92 | /// </summary> | ||
| 93 | /// <param name="document"></param> | ||
| 94 | /// <param name="prefix"></param> | ||
| 95 | /// <returns></returns> | ||
| 96 | public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) | ||
| 97 | { | ||
| 98 | var namespaceManager = new XmlNamespaceManager(document.NameTable); | ||
| 99 | namespaceManager.AddNamespace(prefix, BurnCommon.BurnNamespace); | ||
| 100 | return namespaceManager; | ||
| 101 | } | ||
| 102 | |||
| 103 | /// <summary> | ||
| 104 | /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. | ||
| 105 | /// </summary> | ||
| 106 | /// <param name="baFolderPath"></param> | ||
| 107 | /// <returns></returns> | ||
| 108 | public static XmlDocument LoadBAData(string baFolderPath) | ||
| 109 | { | ||
| 110 | var document = new XmlDocument(); | ||
| 111 | document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName)); | ||
| 112 | return document; | ||
| 113 | } | ||
| 114 | |||
| 115 | /// <summary> | ||
| 116 | /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. | ||
| 117 | /// </summary> | ||
| 118 | /// <param name="baFolderPath"></param> | ||
| 119 | /// <returns></returns> | ||
| 120 | public static XmlDocument LoadBundleExtensionData(string baFolderPath) | ||
| 121 | { | ||
| 122 | var document = new XmlDocument(); | ||
| 123 | document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName)); | ||
| 124 | return document; | ||
| 125 | } | ||
| 126 | |||
| 127 | /// <summary> | ||
| 128 | /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. | ||
| 129 | /// </summary> | ||
| 130 | /// <param name="baFolderPath"></param> | ||
| 131 | /// <returns></returns> | ||
| 132 | public static XmlDocument LoadBurnManifest(string baFolderPath) | ||
| 133 | { | ||
| 134 | var document = new XmlDocument(); | ||
| 135 | document.Load(Path.Combine(baFolderPath, "manifest.xml")); | ||
| 136 | return document; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | } | ||
diff --git a/src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs new file mode 100644 index 00000000..277861ff --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs | |||
| @@ -0,0 +1,116 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.TestPackage | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Xml; | ||
| 7 | using Xunit; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// The result of extracting the BA container. | ||
| 11 | /// </summary> | ||
| 12 | public class ExtractBAContainerResult | ||
| 13 | { | ||
| 14 | /// <summary> | ||
| 15 | /// <see cref="XmlDocument"/> for BundleExtensionData.xml. | ||
| 16 | /// </summary> | ||
| 17 | public XmlDocument BundleExtensionDataDocument { get; set; } | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// <see cref="XmlNamespaceManager"/> for BundleExtensionData.xml. | ||
| 21 | /// </summary> | ||
| 22 | public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; } | ||
| 23 | |||
| 24 | /// <summary> | ||
| 25 | /// <see cref="XmlDocument"/> for BootstrapperApplicationData.xml. | ||
| 26 | /// </summary> | ||
| 27 | public XmlDocument BADataDocument { get; set; } | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// <see cref="XmlNamespaceManager"/> for BootstrapperApplicationData.xml. | ||
| 31 | /// </summary> | ||
| 32 | public XmlNamespaceManager BADataNamespaceManager { get; set; } | ||
| 33 | |||
| 34 | /// <summary> | ||
| 35 | /// <see cref="XmlDocument"/> for the Burn manifest.xml. | ||
| 36 | /// </summary> | ||
| 37 | public XmlDocument ManifestDocument { get; set; } | ||
| 38 | |||
| 39 | /// <summary> | ||
| 40 | /// <see cref="XmlNamespaceManager"/> for the Burn manifest.xml. | ||
| 41 | /// </summary> | ||
| 42 | public XmlNamespaceManager ManifestNamespaceManager { get; set; } | ||
| 43 | |||
| 44 | /// <summary> | ||
| 45 | /// Whether extraction succeeded. | ||
| 46 | /// </summary> | ||
| 47 | public bool Success { get; set; } | ||
| 48 | |||
| 49 | /// <summary> | ||
| 50 | /// | ||
| 51 | /// </summary> | ||
| 52 | /// <returns></returns> | ||
| 53 | public ExtractBAContainerResult AssertSuccess() | ||
| 54 | { | ||
| 55 | Assert.True(this.Success); | ||
| 56 | return this; | ||
| 57 | } | ||
| 58 | |||
| 59 | /// <summary> | ||
| 60 | /// Returns the relative path of the BA entry point dll in the given folder. | ||
| 61 | /// </summary> | ||
| 62 | /// <param name="extractedBAContainerFolderPath"></param> | ||
| 63 | /// <returns></returns> | ||
| 64 | public string GetBAFilePath(string extractedBAContainerFolderPath) | ||
| 65 | { | ||
| 66 | var uxPayloads = this.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload"); | ||
| 67 | var baPayload = uxPayloads[0]; | ||
| 68 | var relativeBAPath = baPayload.Attributes["FilePath"].Value; | ||
| 69 | return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); | ||
| 70 | } | ||
| 71 | |||
| 72 | /// <summary> | ||
| 73 | /// Returns the relative path of the BundleExtension entry point dll in the given folder. | ||
| 74 | /// </summary> | ||
| 75 | /// <param name="extractedBAContainerFolderPath"></param> | ||
| 76 | /// <param name="extensionId"></param> | ||
| 77 | /// <returns></returns> | ||
| 78 | public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId) | ||
| 79 | { | ||
| 80 | var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']"); | ||
| 81 | var bextPayload = uxPayloads[0]; | ||
| 82 | var relativeBextPath = bextPayload.Attributes["FilePath"].Value; | ||
| 83 | return Path.Combine(extractedBAContainerFolderPath, relativeBextPath); | ||
| 84 | } | ||
| 85 | |||
| 86 | /// <summary> | ||
| 87 | /// | ||
| 88 | /// </summary> | ||
| 89 | /// <param name="xpath">elements must have the 'ba' prefix</param> | ||
| 90 | /// <returns></returns> | ||
| 91 | public XmlNodeList SelectBADataNodes(string xpath) | ||
| 92 | { | ||
| 93 | return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager); | ||
| 94 | } | ||
| 95 | |||
| 96 | /// <summary> | ||
| 97 | /// | ||
| 98 | /// </summary> | ||
| 99 | /// <param name="xpath">elements must have the 'be' prefix</param> | ||
| 100 | /// <returns></returns> | ||
| 101 | public XmlNodeList SelectBundleExtensionDataNodes(string xpath) | ||
| 102 | { | ||
| 103 | return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager); | ||
| 104 | } | ||
| 105 | |||
| 106 | /// <summary> | ||
| 107 | /// | ||
| 108 | /// </summary> | ||
| 109 | /// <param name="xpath">elements must have the 'burn' prefix</param> | ||
| 110 | /// <returns></returns> | ||
| 111 | public XmlNodeList SelectManifestNodes(string xpath) | ||
| 112 | { | ||
| 113 | return this.ManifestDocument.SelectNodes(xpath, this.ManifestNamespaceManager); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
diff --git a/src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs b/src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs new file mode 100644 index 00000000..7040fe82 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | using System.Collections.Generic; | ||
| 2 | using WixToolset.Data; | ||
| 3 | using WixToolset.Extensibility; | ||
| 4 | using WixToolset.Extensibility.Services; | ||
| 5 | |||
| 6 | namespace WixToolset.Core.TestPackage | ||
| 7 | { | ||
| 8 | /// <summary> | ||
| 9 | /// An <see cref="IMessageListener"/> that simply stores all the messages. | ||
| 10 | /// </summary> | ||
| 11 | public sealed class TestMessageListener : IMessageListener | ||
| 12 | { | ||
| 13 | /// <summary> | ||
| 14 | /// All messages that have been received. | ||
| 15 | /// </summary> | ||
| 16 | public List<Message> Messages { get; } = new List<Message>(); | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// | ||
| 20 | /// </summary> | ||
| 21 | public string ShortAppName => "TEST"; | ||
| 22 | |||
| 23 | /// <summary> | ||
| 24 | /// | ||
| 25 | /// </summary> | ||
| 26 | public string LongAppName => "Test"; | ||
| 27 | |||
| 28 | /// <summary> | ||
| 29 | /// Stores the message in <see cref="Messages"/>. | ||
| 30 | /// </summary> | ||
| 31 | /// <param name="message"></param> | ||
| 32 | public void Write(Message message) | ||
| 33 | { | ||
| 34 | this.Messages.Add(message); | ||
| 35 | } | ||
| 36 | |||
| 37 | /// <summary> | ||
| 38 | /// Stores the message in <see cref="Messages"/>. | ||
| 39 | /// </summary> | ||
| 40 | /// <param name="message"></param> | ||
| 41 | public void Write(string message) | ||
| 42 | { | ||
| 43 | this.Messages.Add(new Message(null, MessageLevel.Information, 0, message)); | ||
| 44 | } | ||
| 45 | |||
| 46 | /// <summary> | ||
| 47 | /// Always returns defaultMessageLevel. | ||
| 48 | /// </summary> | ||
| 49 | /// <param name="messaging"></param> | ||
| 50 | /// <param name="message"></param> | ||
| 51 | /// <param name="defaultMessageLevel"></param> | ||
| 52 | /// <returns></returns> | ||
| 53 | public MessageLevel CalculateMessageLevel(IMessaging messaging, Message message, MessageLevel defaultMessageLevel) => defaultMessageLevel; | ||
| 54 | } | ||
| 55 | } | ||
diff --git a/src/wix/WixToolset.Core.TestPackage/WixRunner.cs b/src/wix/WixToolset.Core.TestPackage/WixRunner.cs new file mode 100644 index 00000000..ed7c49b8 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/WixRunner.cs | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.TestPackage | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Threading; | ||
| 8 | using System.Threading.Tasks; | ||
| 9 | using WixToolset.Core.Burn; | ||
| 10 | using WixToolset.Core.WindowsInstaller; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Utility class to emulate wix.exe with standard backends. | ||
| 16 | /// </summary> | ||
| 17 | public static class WixRunner | ||
| 18 | { | ||
| 19 | /// <summary> | ||
| 20 | /// Emulates calling wix.exe with standard backends. | ||
| 21 | /// </summary> | ||
| 22 | /// <param name="args"></param> | ||
| 23 | /// <param name="messages"></param> | ||
| 24 | /// <param name="warningsAsErrors"></param> | ||
| 25 | /// <returns></returns> | ||
| 26 | public static int Execute(string[] args, out List<Message> messages, bool warningsAsErrors = true) | ||
| 27 | { | ||
| 28 | var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); | ||
| 29 | var task = Execute(args, serviceProvider, out messages, warningsAsErrors: warningsAsErrors); | ||
| 30 | return task.Result; | ||
| 31 | } | ||
| 32 | |||
| 33 | /// <summary> | ||
| 34 | /// Emulates calling wix.exe with standard backends. | ||
| 35 | /// This overload always treats warnings as errors. | ||
| 36 | /// </summary> | ||
| 37 | /// <param name="args"></param> | ||
| 38 | /// <returns></returns> | ||
| 39 | public static WixRunnerResult Execute(params string[] args) | ||
| 40 | { | ||
| 41 | return Execute(true, args); | ||
| 42 | } | ||
| 43 | |||
| 44 | /// <summary> | ||
| 45 | /// Emulates calling wix.exe with standard backends. | ||
| 46 | /// </summary> | ||
| 47 | /// <param name="warningsAsErrors"></param> | ||
| 48 | /// <param name="args"></param> | ||
| 49 | /// <returns></returns> | ||
| 50 | public static WixRunnerResult Execute(bool warningsAsErrors, params string[] args) | ||
| 51 | { | ||
| 52 | var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); | ||
| 53 | var exitCode = Execute(args, serviceProvider, out var messages, warningsAsErrors: warningsAsErrors); | ||
| 54 | return new WixRunnerResult { ExitCode = exitCode.Result, Messages = messages.ToArray() }; | ||
| 55 | } | ||
| 56 | |||
| 57 | /// <summary> | ||
| 58 | /// Emulates calling wix.exe with standard backends. | ||
| 59 | /// </summary> | ||
| 60 | /// <param name="args"></param> | ||
| 61 | /// <param name="coreProvider"></param> | ||
| 62 | /// <param name="messages"></param> | ||
| 63 | /// <param name="warningsAsErrors"></param> | ||
| 64 | /// <returns></returns> | ||
| 65 | public static Task<int> Execute(string[] args, IWixToolsetCoreServiceProvider coreProvider, out List<Message> messages, bool warningsAsErrors = true) | ||
| 66 | { | ||
| 67 | coreProvider.AddWindowsInstallerBackend() | ||
| 68 | .AddBundleBackend(); | ||
| 69 | |||
| 70 | var listener = new TestMessageListener(); | ||
| 71 | |||
| 72 | messages = listener.Messages; | ||
| 73 | |||
| 74 | var messaging = coreProvider.GetService<IMessaging>(); | ||
| 75 | messaging.SetListener(listener); | ||
| 76 | |||
| 77 | var arguments = new List<string>(args); | ||
| 78 | if (warningsAsErrors) | ||
| 79 | { | ||
| 80 | arguments.Add("-wx"); | ||
| 81 | } | ||
| 82 | |||
| 83 | var commandLine = coreProvider.GetService<ICommandLine>(); | ||
| 84 | var command = commandLine.CreateCommand(arguments.ToArray()); | ||
| 85 | return command?.ExecuteAsync(CancellationToken.None) ?? Task.FromResult(1); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
diff --git a/src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs b/src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs new file mode 100644 index 00000000..6a3d714c --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.TestPackage | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using Xunit; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// The result of an Execute method of <see cref="WixRunner"/>. | ||
| 12 | /// </summary> | ||
| 13 | public class WixRunnerResult | ||
| 14 | { | ||
| 15 | /// <summary> | ||
| 16 | /// ExitCode for the operation. | ||
| 17 | /// </summary> | ||
| 18 | public int ExitCode { get; set; } | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Messages from the operation. | ||
| 22 | /// </summary> | ||
| 23 | public Message[] Messages { get; set; } | ||
| 24 | |||
| 25 | /// <summary> | ||
| 26 | /// | ||
| 27 | /// </summary> | ||
| 28 | /// <returns></returns> | ||
| 29 | public WixRunnerResult AssertSuccess() | ||
| 30 | { | ||
| 31 | AssertSuccess(this.ExitCode, this.Messages); | ||
| 32 | return this; | ||
| 33 | } | ||
| 34 | |||
| 35 | /// <summary> | ||
| 36 | /// | ||
| 37 | /// </summary> | ||
| 38 | /// <param name="exitCode"></param> | ||
| 39 | /// <param name="messages"></param> | ||
| 40 | public static void AssertSuccess(int exitCode, IEnumerable<Message> messages) | ||
| 41 | { | ||
| 42 | Assert.True(0 == exitCode, $"\r\n\r\nWixRunner failed with exit code: {exitCode}\r\n Output: {String.Join("\r\n ", FormatMessages(messages))}\r\n"); | ||
| 43 | } | ||
| 44 | |||
| 45 | private static IEnumerable<string> FormatMessages(IEnumerable<Message> messages) | ||
| 46 | { | ||
| 47 | foreach (var message in messages) | ||
| 48 | { | ||
| 49 | var filename = message.SourceLineNumbers?.FileName ?? "TEST"; | ||
| 50 | var line = message.SourceLineNumbers?.LineNumber ?? -1; | ||
| 51 | var type = message.Level.ToString().ToLowerInvariant(); | ||
| 52 | |||
| 53 | if (line > 0) | ||
| 54 | { | ||
| 55 | filename = String.Concat(filename, "(", line, ")"); | ||
| 56 | } | ||
| 57 | |||
| 58 | yield return String.Format("{0} : {1} {2}{3:0000}: {4}", filename, type, "TEST", message.Id, message.ToString()); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj b/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj new file mode 100644 index 00000000..b64b4075 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFrameworks>netstandard2.0</TargetFrameworks> | ||
| 7 | <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks> | ||
| 8 | <Description>Internal WiX Toolset Test Package</Description> | ||
| 9 | <DebugType>embedded</DebugType> | ||
| 10 | <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
| 11 | <CreateDocumentationFile>true</CreateDocumentationFile> | ||
| 12 | </PropertyGroup> | ||
| 13 | |||
| 14 | <ItemGroup> | ||
| 15 | <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" IncludeAssets="true" /> | ||
| 16 | <ProjectReference Include="..\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj" IncludeAssets="true" /> | ||
| 17 | <ProjectReference Include="..\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj" IncludeAssets="true" /> | ||
| 18 | </ItemGroup> | ||
| 19 | |||
| 20 | <ItemGroup> | ||
| 21 | <PackageReference Include="WixToolset.Data" Version="4.0.*" /> | ||
| 22 | <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" /> | ||
| 23 | <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" /> | ||
| 24 | </ItemGroup> | ||
| 25 | |||
| 26 | <ItemGroup> | ||
| 27 | <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | ||
| 28 | <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" /> | ||
| 29 | </ItemGroup> | ||
| 30 | |||
| 31 | <ItemGroup> | ||
| 32 | <PackageReference Include="xunit.assert" Version="2.4.0" /> | ||
| 33 | </ItemGroup> | ||
| 34 | </Project> | ||
diff --git a/src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs b/src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs new file mode 100644 index 00000000..f4966f74 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs | |||
| @@ -0,0 +1,90 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.TestPackage | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.IO; | ||
| 7 | using System.Text.RegularExpressions; | ||
| 8 | using System.Xml; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Utility class to help compare XML in tests using string comparisons by using single quotes and stripping all namespaces. | ||
| 12 | /// </summary> | ||
| 13 | public static class XmlNodeExtensions | ||
| 14 | { | ||
| 15 | /// <summary> | ||
| 16 | /// Returns the node's outer XML using single quotes and stripping all namespaces. | ||
| 17 | /// </summary> | ||
| 18 | /// <param name="node"></param> | ||
| 19 | /// <param name="ignoredAttributesByElementName">Attributes for which the value should be set to '*'.</param> | ||
| 20 | /// <returns></returns> | ||
| 21 | public static string GetTestXml(this XmlNode node, Dictionary<string, List<string>> ignoredAttributesByElementName = null) | ||
| 22 | { | ||
| 23 | return node.OuterXml.GetTestXml(ignoredAttributesByElementName); | ||
| 24 | } | ||
| 25 | |||
| 26 | /// <summary> | ||
| 27 | /// Returns the XML using single quotes and stripping all namespaces. | ||
| 28 | /// </summary> | ||
| 29 | /// <param name="xml"></param> | ||
| 30 | /// <param name="ignoredAttributesByElementName">Attributes for which the value should be set to '*'.</param> | ||
| 31 | /// <returns></returns> | ||
| 32 | public static string GetTestXml(this string xml, Dictionary<string, List<string>> ignoredAttributesByElementName = null) | ||
| 33 | { | ||
| 34 | string formattedXml; | ||
| 35 | using (var sw = new StringWriter()) | ||
| 36 | using (var writer = new TestXmlWriter(sw)) | ||
| 37 | { | ||
| 38 | var doc = new XmlDocument(); | ||
| 39 | doc.LoadXml(xml); | ||
| 40 | |||
| 41 | if (ignoredAttributesByElementName != null) | ||
| 42 | { | ||
| 43 | HandleIgnoredAttributes(doc, ignoredAttributesByElementName); | ||
| 44 | } | ||
| 45 | |||
| 46 | doc.Save(writer); | ||
| 47 | formattedXml = sw.ToString(); | ||
| 48 | } | ||
| 49 | |||
| 50 | return Regex.Replace(formattedXml, " xmlns(:[^=]+)?='[^']*'", ""); | ||
| 51 | } | ||
| 52 | |||
| 53 | private static void HandleIgnoredAttributes(XmlNode node, Dictionary<string, List<string>> ignoredAttributesByElementName) | ||
| 54 | { | ||
| 55 | if (node.Attributes != null && ignoredAttributesByElementName.TryGetValue(node.LocalName, out var ignoredAttributes)) | ||
| 56 | { | ||
| 57 | foreach (var ignoredAttribute in ignoredAttributes) | ||
| 58 | { | ||
| 59 | var attribute = node.Attributes[ignoredAttribute]; | ||
| 60 | if (attribute != null) | ||
| 61 | { | ||
| 62 | attribute.Value = "*"; | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | if (node.ChildNodes != null) | ||
| 68 | { | ||
| 69 | foreach (XmlNode childNode in node.ChildNodes) | ||
| 70 | { | ||
| 71 | HandleIgnoredAttributes(childNode, ignoredAttributesByElementName); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | private class TestXmlWriter : XmlTextWriter | ||
| 77 | { | ||
| 78 | public TestXmlWriter(TextWriter w) | ||
| 79 | : base(w) | ||
| 80 | { | ||
| 81 | this.QuoteChar = '\''; | ||
| 82 | } | ||
| 83 | |||
| 84 | public override void WriteStartDocument() | ||
| 85 | { | ||
| 86 | //OmitXmlDeclaration | ||
| 87 | } | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs new file mode 100644 index 00000000..cbba6030 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data.Symbols; | ||
| 8 | using WixToolset.Data.WindowsInstaller; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Add back possibly suppressed sequence tables since all sequence tables must be present | ||
| 12 | /// for the merge process to work. We'll drop the suppressed sequence tables again as | ||
| 13 | /// necessary. | ||
| 14 | /// </summary> | ||
| 15 | internal class AddBackSuppressedSequenceTablesCommand | ||
| 16 | { | ||
| 17 | public AddBackSuppressedSequenceTablesCommand(WindowsInstallerData output, TableDefinitionCollection tableDefinitions) | ||
| 18 | { | ||
| 19 | this.Output = output; | ||
| 20 | this.TableDefinitions = tableDefinitions; | ||
| 21 | } | ||
| 22 | |||
| 23 | private WindowsInstallerData Output { get; } | ||
| 24 | |||
| 25 | private TableDefinitionCollection TableDefinitions { get; } | ||
| 26 | |||
| 27 | public IEnumerable<string> SuppressedTableNames { get; private set; } | ||
| 28 | |||
| 29 | public IEnumerable<string> Execute() | ||
| 30 | { | ||
| 31 | var suppressedTableNames = new HashSet<string>(); | ||
| 32 | |||
| 33 | foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) | ||
| 34 | { | ||
| 35 | var sequenceTableName = sequence.WindowsInstallerTableName(); | ||
| 36 | var sequenceTable = this.Output.Tables[sequenceTableName]; | ||
| 37 | |||
| 38 | if (null == sequenceTable) | ||
| 39 | { | ||
| 40 | sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); | ||
| 41 | } | ||
| 42 | |||
| 43 | if (0 == sequenceTable.Rows.Count) | ||
| 44 | { | ||
| 45 | suppressedTableNames.Add(sequenceTableName); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | return this.SuppressedTableNames = suppressedTableNames; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs new file mode 100644 index 00000000..c4fddb3e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.Linq; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Symbols; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Add CreateFolder symbols, if not already present, for null-keypath components. | ||
| 12 | /// </summary> | ||
| 13 | internal class AddCreateFoldersCommand | ||
| 14 | { | ||
| 15 | internal AddCreateFoldersCommand(IntermediateSection section) | ||
| 16 | { | ||
| 17 | this.Section = section; | ||
| 18 | } | ||
| 19 | |||
| 20 | private IntermediateSection Section { get; } | ||
| 21 | |||
| 22 | public void Execute() | ||
| 23 | { | ||
| 24 | var createFolderSymbolsByComponentRef = new HashSet<string>(this.Section.Symbols.OfType<CreateFolderSymbol>().Select(t => t.ComponentRef)); | ||
| 25 | foreach (var componentSymbol in this.Section.Symbols.OfType<ComponentSymbol>().Where(t => t.KeyPathType == ComponentKeyPathType.Directory).ToList()) | ||
| 26 | { | ||
| 27 | if (!createFolderSymbolsByComponentRef.Contains(componentSymbol.Id.Id)) | ||
| 28 | { | ||
| 29 | this.Section.AddSymbol(new CreateFolderSymbol(componentSymbol.SourceLineNumbers) | ||
| 30 | { | ||
| 31 | DirectoryRef = componentSymbol.DirectoryRef, | ||
| 32 | ComponentRef = componentSymbol.Id.Id, | ||
| 33 | }); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
| 38 | } \ No newline at end of file | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs new file mode 100644 index 00000000..ee3bcc91 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs | |||
| @@ -0,0 +1,95 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Data.WindowsInstaller; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Add referenced standard directory symbols, if not already present. | ||
| 14 | /// </summary> | ||
| 15 | internal class AddRequiredStandardDirectories | ||
| 16 | { | ||
| 17 | internal AddRequiredStandardDirectories(IntermediateSection section, Platform platform) | ||
| 18 | { | ||
| 19 | this.Section = section; | ||
| 20 | this.Platform = platform; | ||
| 21 | } | ||
| 22 | |||
| 23 | private IntermediateSection Section { get; } | ||
| 24 | |||
| 25 | private Platform Platform { get; } | ||
| 26 | |||
| 27 | public void Execute() | ||
| 28 | { | ||
| 29 | var directories = this.Section.Symbols.OfType<DirectorySymbol>().ToList(); | ||
| 30 | var directoryIds = new SortedSet<string>(directories.Select(d => d.Id.Id)); | ||
| 31 | |||
| 32 | foreach (var directory in directories) | ||
| 33 | { | ||
| 34 | var parentDirectoryId = directory.ParentDirectoryRef; | ||
| 35 | |||
| 36 | if (String.IsNullOrEmpty(parentDirectoryId)) | ||
| 37 | { | ||
| 38 | if (directory.Id.Id != "TARGETDIR") | ||
| 39 | { | ||
| 40 | directory.ParentDirectoryRef = "TARGETDIR"; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | else | ||
| 44 | { | ||
| 45 | this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, directory.SourceLineNumbers); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | if (!directoryIds.Contains("TARGETDIR") && WindowsInstallerStandard.TryGetStandardDirectory("TARGETDIR", out var targetDir)) | ||
| 50 | { | ||
| 51 | directoryIds.Add(targetDir.Id.Id); | ||
| 52 | this.Section.AddSymbol(targetDir); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | private void EnsureStandardDirectoryAdded(ISet<string> directoryIds, string directoryId, SourceLineNumber sourceLineNumbers) | ||
| 57 | { | ||
| 58 | if (!directoryIds.Contains(directoryId) && WindowsInstallerStandard.TryGetStandardDirectory(directoryId, out var standardDirectory)) | ||
| 59 | { | ||
| 60 | var parentDirectoryId = this.GetStandardDirectoryParent(directoryId); | ||
| 61 | |||
| 62 | var directory = new DirectorySymbol(sourceLineNumbers, standardDirectory.Id) | ||
| 63 | { | ||
| 64 | Name = standardDirectory.Name, | ||
| 65 | ParentDirectoryRef = parentDirectoryId, | ||
| 66 | }; | ||
| 67 | |||
| 68 | directoryIds.Add(directory.Id.Id); | ||
| 69 | this.Section.AddSymbol(directory); | ||
| 70 | |||
| 71 | if (!String.IsNullOrEmpty(parentDirectoryId)) | ||
| 72 | { | ||
| 73 | this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, sourceLineNumbers); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | private string GetStandardDirectoryParent(string directoryId) | ||
| 79 | { | ||
| 80 | switch (directoryId) | ||
| 81 | { | ||
| 82 | case "TARGETDIR": | ||
| 83 | return null; | ||
| 84 | |||
| 85 | case "CommonFiles6432Folder": | ||
| 86 | case "ProgramFiles6432Folder": | ||
| 87 | case "System6432Folder": | ||
| 88 | return WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directoryId, this.Platform); | ||
| 89 | |||
| 90 | default: | ||
| 91 | return "TARGETDIR"; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs new file mode 100644 index 00000000..759ba303 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Text; | ||
| 7 | |||
| 8 | internal class AssemblyName | ||
| 9 | { | ||
| 10 | public AssemblyName(string name, string culture, string version, string fileVersion, string architecture, string publicKeyToken, string type) | ||
| 11 | { | ||
| 12 | this.Name = name; | ||
| 13 | this.Culture = culture ?? "neutral"; | ||
| 14 | this.Version = version; | ||
| 15 | this.FileVersion = fileVersion; | ||
| 16 | this.Architecture = architecture; | ||
| 17 | |||
| 18 | this.StrongNamedSigned = !String.IsNullOrEmpty(publicKeyToken); | ||
| 19 | this.PublicKeyToken = publicKeyToken; | ||
| 20 | this.Type = type; | ||
| 21 | } | ||
| 22 | |||
| 23 | public string Name { get; } | ||
| 24 | |||
| 25 | public string Culture { get; } | ||
| 26 | |||
| 27 | public string Version { get; } | ||
| 28 | |||
| 29 | public string FileVersion { get; } | ||
| 30 | |||
| 31 | public string Architecture { get; } | ||
| 32 | |||
| 33 | public string PublicKeyToken { get; } | ||
| 34 | |||
| 35 | public bool StrongNamedSigned { get; } | ||
| 36 | |||
| 37 | public string Type { get; } | ||
| 38 | |||
| 39 | public string GetFullName() | ||
| 40 | { | ||
| 41 | var assemblyName = new StringBuilder(); | ||
| 42 | |||
| 43 | assemblyName.Append(this.Name); | ||
| 44 | assemblyName.Append(", Version="); | ||
| 45 | assemblyName.Append(this.Version); | ||
| 46 | assemblyName.Append(", Culture="); | ||
| 47 | assemblyName.Append(this.Culture); | ||
| 48 | assemblyName.Append(", PublicKeyToken="); | ||
| 49 | assemblyName.Append(this.PublicKeyToken ?? "null"); | ||
| 50 | |||
| 51 | if (!String.IsNullOrEmpty(this.Architecture)) | ||
| 52 | { | ||
| 53 | assemblyName.Append(", ProcessorArchitecture="); | ||
| 54 | assemblyName.Append(this.Architecture); | ||
| 55 | } | ||
| 56 | |||
| 57 | return assemblyName.ToString(); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs new file mode 100644 index 00000000..2103cd32 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs | |||
| @@ -0,0 +1,214 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Reflection.Metadata; | ||
| 8 | using System.Reflection.PortableExecutable; | ||
| 9 | using System.Security.Cryptography; | ||
| 10 | using System.Text; | ||
| 11 | using System.Xml; | ||
| 12 | using System.Xml.XPath; | ||
| 13 | using WixToolset.Data; | ||
| 14 | |||
| 15 | internal static class AssemblyNameReader | ||
| 16 | { | ||
| 17 | public static AssemblyName ReadAssembly(SourceLineNumber sourceLineNumbers, string assemblyPath, string fileVersion) | ||
| 18 | { | ||
| 19 | try | ||
| 20 | { | ||
| 21 | using (var stream = File.OpenRead(assemblyPath)) | ||
| 22 | using (var peReader = new PEReader(stream)) | ||
| 23 | { | ||
| 24 | var reader = peReader.GetMetadataReader(); | ||
| 25 | var headers = peReader.PEHeaders; | ||
| 26 | |||
| 27 | var assembly = reader.GetAssemblyDefinition(); | ||
| 28 | var attributes = assembly.GetCustomAttributes(); | ||
| 29 | |||
| 30 | var name = ReadString(reader, assembly.Name); | ||
| 31 | var culture = ReadString(reader, assembly.Culture); | ||
| 32 | var architecture = ArchitectureFromHeaders(headers); | ||
| 33 | var version = assembly.Version.ToString(); | ||
| 34 | var publicKeyToken = ReadPublicKeyToken(reader, assembly.PublicKey); | ||
| 35 | |||
| 36 | // There is a bug in v1 fusion that requires the assembly's "version" attribute | ||
| 37 | // to be equal to or longer than the "fileVersion" in length when its present; | ||
| 38 | // the workaround is to prepend zeroes to the last version number in the assembly | ||
| 39 | // version. | ||
| 40 | var targetNetfx1 = (headers.CorHeader.MajorRuntimeVersion == 2) && (headers.CorHeader.MinorRuntimeVersion == 0); | ||
| 41 | if (targetNetfx1 && !String.IsNullOrEmpty(fileVersion) && fileVersion.Length > version.Length) | ||
| 42 | { | ||
| 43 | var versionParts = version.Split('.'); | ||
| 44 | |||
| 45 | if (versionParts.Length > 0) | ||
| 46 | { | ||
| 47 | var padding = new string('0', fileVersion.Length - version.Length); | ||
| 48 | |||
| 49 | versionParts[versionParts.Length - 1] = String.Concat(padding, versionParts[versionParts.Length - 1]); | ||
| 50 | version = String.Join(".", versionParts); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | return new AssemblyName(name, culture, version, fileVersion, architecture, publicKeyToken, null); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | catch (Exception e) when (e is FileNotFoundException || e is BadImageFormatException || e is InvalidOperationException) | ||
| 58 | { | ||
| 59 | throw new WixException(ErrorMessages.InvalidAssemblyFile(sourceLineNumbers, assemblyPath, $"{e.GetType().Name}: {e.Message}")); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | public static AssemblyName ReadAssemblyManifest(SourceLineNumber sourceLineNumbers, string manifestPath) | ||
| 64 | { | ||
| 65 | string win32Type = null; | ||
| 66 | string win32Name = null; | ||
| 67 | string win32Version = null; | ||
| 68 | string win32ProcessorArchitecture = null; | ||
| 69 | string win32PublicKeyToken = null; | ||
| 70 | |||
| 71 | // Loading the dom is expensive we want more performant APIs than the DOM | ||
| 72 | // Navigator is cheaper than dom. Perhaps there is a cheaper API still. | ||
| 73 | try | ||
| 74 | { | ||
| 75 | var doc = new XPathDocument(manifestPath); | ||
| 76 | var nav = doc.CreateNavigator(); | ||
| 77 | nav.MoveToRoot(); | ||
| 78 | |||
| 79 | // This assumes a particular schema for a win32 manifest and does not | ||
| 80 | // provide error checking if the file does not conform to schema. | ||
| 81 | // The fallback case here is that nothing is added to the MsiAssemblyName | ||
| 82 | // table for an out of tolerance Win32 manifest. Perhaps warnings needed. | ||
| 83 | if (nav.MoveToFirstChild()) | ||
| 84 | { | ||
| 85 | while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") | ||
| 86 | { | ||
| 87 | nav.MoveToNext(); | ||
| 88 | } | ||
| 89 | |||
| 90 | if (nav.MoveToFirstChild()) | ||
| 91 | { | ||
| 92 | var hasNextSibling = true; | ||
| 93 | while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) | ||
| 94 | { | ||
| 95 | hasNextSibling = nav.MoveToNext(); | ||
| 96 | } | ||
| 97 | |||
| 98 | if (!hasNextSibling) | ||
| 99 | { | ||
| 100 | throw new WixException(ErrorMessages.InvalidManifestContent(sourceLineNumbers, manifestPath)); | ||
| 101 | } | ||
| 102 | |||
| 103 | if (nav.MoveToAttribute("type", String.Empty)) | ||
| 104 | { | ||
| 105 | win32Type = nav.Value; | ||
| 106 | nav.MoveToParent(); | ||
| 107 | } | ||
| 108 | |||
| 109 | if (nav.MoveToAttribute("name", String.Empty)) | ||
| 110 | { | ||
| 111 | win32Name = nav.Value; | ||
| 112 | nav.MoveToParent(); | ||
| 113 | } | ||
| 114 | |||
| 115 | if (nav.MoveToAttribute("version", String.Empty)) | ||
| 116 | { | ||
| 117 | win32Version = nav.Value; | ||
| 118 | nav.MoveToParent(); | ||
| 119 | } | ||
| 120 | |||
| 121 | if (nav.MoveToAttribute("processorArchitecture", String.Empty)) | ||
| 122 | { | ||
| 123 | win32ProcessorArchitecture = nav.Value; | ||
| 124 | nav.MoveToParent(); | ||
| 125 | } | ||
| 126 | |||
| 127 | if (nav.MoveToAttribute("publicKeyToken", String.Empty)) | ||
| 128 | { | ||
| 129 | win32PublicKeyToken = nav.Value; | ||
| 130 | nav.MoveToParent(); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | catch (FileNotFoundException fe) | ||
| 136 | { | ||
| 137 | throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, fe.FileName, "AssemblyManifest")); | ||
| 138 | } | ||
| 139 | catch (XmlException xe) | ||
| 140 | { | ||
| 141 | throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "manifest", xe.Message)); | ||
| 142 | } | ||
| 143 | |||
| 144 | return new AssemblyName(win32Name, null, win32Version, null, win32ProcessorArchitecture, win32PublicKeyToken, win32Type); | ||
| 145 | } | ||
| 146 | |||
| 147 | private static string ArchitectureFromHeaders(PEHeaders headers) | ||
| 148 | { | ||
| 149 | if (headers.PEHeader.Magic == PEMagic.PE32Plus) | ||
| 150 | { | ||
| 151 | return "AMD64"; | ||
| 152 | } | ||
| 153 | else if ((headers.CorHeader.Flags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit) | ||
| 154 | { | ||
| 155 | return "x86"; | ||
| 156 | } | ||
| 157 | else if ((headers.CorHeader.Flags & CorFlags.ILOnly) == CorFlags.ILOnly) | ||
| 158 | { | ||
| 159 | return "MSIL"; | ||
| 160 | } | ||
| 161 | else | ||
| 162 | { | ||
| 163 | // We return "x86" here because that seems to best match the Fusion-based | ||
| 164 | // GetAssemblyIdentityFromFile() method of acquiring the assembly identity. | ||
| 165 | return "x86"; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | private static string ReadString(MetadataReader reader, StringHandle handle) | ||
| 170 | { | ||
| 171 | return handle.IsNil ? null : reader.GetString(handle); | ||
| 172 | } | ||
| 173 | |||
| 174 | private static string ReadPublicKeyToken(MetadataReader reader, BlobHandle handle) | ||
| 175 | { | ||
| 176 | if (handle.IsNil) | ||
| 177 | { | ||
| 178 | return null; | ||
| 179 | } | ||
| 180 | |||
| 181 | var bytes = reader.GetBlobBytes(handle); | ||
| 182 | if (bytes.Length == 0) | ||
| 183 | { | ||
| 184 | return null; | ||
| 185 | } | ||
| 186 | |||
| 187 | var result = new StringBuilder(); | ||
| 188 | |||
| 189 | // If we have the full public key, calculate the public key token from the | ||
| 190 | // last 8 bytes (in reverse order) of the public key's SHA1 hash. | ||
| 191 | if (bytes.Length > 8) | ||
| 192 | { | ||
| 193 | using (var sha1 = SHA1.Create()) | ||
| 194 | { | ||
| 195 | var hash = sha1.ComputeHash(bytes); | ||
| 196 | |||
| 197 | for (var i = 1; i <= 8; ++i) | ||
| 198 | { | ||
| 199 | result.Append(hash[hash.Length - i].ToString("X2")); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | else | ||
| 204 | { | ||
| 205 | foreach (var b in bytes) | ||
| 206 | { | ||
| 207 | result.Append(b.ToString("X2")); | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | return result.ToString(); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs new file mode 100644 index 00000000..cfa84629 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs | |||
| @@ -0,0 +1,302 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. | ||
| 16 | /// </summary> | ||
| 17 | internal class AssignMediaCommand | ||
| 18 | { | ||
| 19 | private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB | ||
| 20 | |||
| 21 | public AssignMediaCommand(IntermediateSection section, IMessaging messaging, IEnumerable<IFileFacade> fileFacades, bool compressed) | ||
| 22 | { | ||
| 23 | this.CabinetNameTemplate = "Cab{0}.cab"; | ||
| 24 | this.Section = section; | ||
| 25 | this.Messaging = messaging; | ||
| 26 | this.FileFacades = fileFacades; | ||
| 27 | this.FilesCompressed = compressed; | ||
| 28 | } | ||
| 29 | |||
| 30 | private IntermediateSection Section { get; } | ||
| 31 | |||
| 32 | private IMessaging Messaging { get; } | ||
| 33 | |||
| 34 | private IEnumerable<IFileFacade> FileFacades { get; } | ||
| 35 | |||
| 36 | private bool FilesCompressed { get; } | ||
| 37 | |||
| 38 | private string CabinetNameTemplate { get; set; } | ||
| 39 | |||
| 40 | /// <summary> | ||
| 41 | /// Gets cabinets with their file rows. | ||
| 42 | /// </summary> | ||
| 43 | public Dictionary<MediaSymbol, IEnumerable<IFileFacade>> FileFacadesByCabinetMedia { get; private set; } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. | ||
| 47 | /// This contains all the files when Package element is marked with compression=no | ||
| 48 | /// </summary> | ||
| 49 | public IEnumerable<IFileFacade> UncompressedFileFacades { get; private set; } | ||
| 50 | |||
| 51 | public void Execute() | ||
| 52 | { | ||
| 53 | var mediaSymbols = this.Section.Symbols.OfType<MediaSymbol>().ToList(); | ||
| 54 | var mediaTemplateSymbols = this.Section.Symbols.OfType<WixMediaTemplateSymbol>().ToList(); | ||
| 55 | |||
| 56 | // If both symbols are authored, it is an error. | ||
| 57 | if (mediaTemplateSymbols.Count > 0 && mediaSymbols.Count > 1) | ||
| 58 | { | ||
| 59 | throw new WixException(ErrorMessages.MediaTableCollision(null)); | ||
| 60 | } | ||
| 61 | |||
| 62 | // If neither symbol is authored, default to a media template. | ||
| 63 | if (SectionType.Product == this.Section.Type && mediaTemplateSymbols.Count == 0 && mediaSymbols.Count == 0) | ||
| 64 | { | ||
| 65 | var mediaTemplate = new WixMediaTemplateSymbol() | ||
| 66 | { | ||
| 67 | CabinetTemplate = "cab{0}.cab", | ||
| 68 | }; | ||
| 69 | |||
| 70 | this.Section.AddSymbol(mediaTemplate); | ||
| 71 | mediaTemplateSymbols.Add(mediaTemplate); | ||
| 72 | } | ||
| 73 | |||
| 74 | // When building merge module, all the files go to "#MergeModule.CABinet". | ||
| 75 | if (SectionType.Module == this.Section.Type) | ||
| 76 | { | ||
| 77 | var mergeModuleMediaSymbol = this.Section.AddSymbol(new MediaSymbol | ||
| 78 | { | ||
| 79 | Cabinet = "#MergeModule.CABinet", | ||
| 80 | }); | ||
| 81 | |||
| 82 | this.FileFacadesByCabinetMedia = new Dictionary<MediaSymbol, IEnumerable<IFileFacade>> | ||
| 83 | { | ||
| 84 | { mergeModuleMediaSymbol, this.FileFacades } | ||
| 85 | }; | ||
| 86 | |||
| 87 | this.UncompressedFileFacades = Array.Empty<IFileFacade>(); | ||
| 88 | } | ||
| 89 | else | ||
| 90 | { | ||
| 91 | var filesByCabinetMedia = new Dictionary<MediaSymbol, List<IFileFacade>>(); | ||
| 92 | var uncompressedFiles = new List<IFileFacade>(); | ||
| 93 | |||
| 94 | if (mediaTemplateSymbols.Count > 0) | ||
| 95 | { | ||
| 96 | this.AutoAssignFiles(mediaTemplateSymbols, mediaSymbols, filesByCabinetMedia, uncompressedFiles); | ||
| 97 | } | ||
| 98 | else | ||
| 99 | { | ||
| 100 | this.ManuallyAssignFiles(mediaSymbols, filesByCabinetMedia, uncompressedFiles); | ||
| 101 | } | ||
| 102 | |||
| 103 | this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable<IFileFacade>)kvp.Value); | ||
| 104 | |||
| 105 | this.UncompressedFileFacades = uncompressedFiles; | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | /// <summary> | ||
| 110 | /// Assign files to cabinets based on MediaTemplate authoring. | ||
| 111 | /// </summary> | ||
| 112 | private void AutoAssignFiles(List<WixMediaTemplateSymbol> mediaTemplateTable, List<MediaSymbol> mediaSymbols, Dictionary<MediaSymbol, List<IFileFacade>> filesByCabinetMedia, List<IFileFacade> uncompressedFiles) | ||
| 113 | { | ||
| 114 | const int MaxCabIndex = 999; | ||
| 115 | |||
| 116 | ulong currentPreCabSize = 0; | ||
| 117 | ulong maxPreCabSizeInBytes; | ||
| 118 | var maxPreCabSizeInMB = 0; | ||
| 119 | var currentCabIndex = 0; | ||
| 120 | |||
| 121 | MediaSymbol currentMediaRow = null; | ||
| 122 | |||
| 123 | // Remove all previous media symbols since they will be replaced with | ||
| 124 | // media template. | ||
| 125 | foreach (var mediaSymbol in mediaSymbols) | ||
| 126 | { | ||
| 127 | this.Section.RemoveSymbol(mediaSymbol); | ||
| 128 | } | ||
| 129 | |||
| 130 | // Auto assign files to cabinets based on maximum uncompressed media size | ||
| 131 | var mediaTemplateRow = mediaTemplateTable.Single(); | ||
| 132 | |||
| 133 | if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) | ||
| 134 | { | ||
| 135 | this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; | ||
| 136 | } | ||
| 137 | |||
| 138 | var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); | ||
| 139 | |||
| 140 | try | ||
| 141 | { | ||
| 142 | // Override authored mums value if environment variable is authored. | ||
| 143 | if (!String.IsNullOrEmpty(mumsString)) | ||
| 144 | { | ||
| 145 | maxPreCabSizeInMB = Int32.Parse(mumsString); | ||
| 146 | } | ||
| 147 | else | ||
| 148 | { | ||
| 149 | maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; | ||
| 150 | } | ||
| 151 | |||
| 152 | maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; | ||
| 153 | } | ||
| 154 | catch (FormatException) | ||
| 155 | { | ||
| 156 | throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); | ||
| 157 | } | ||
| 158 | catch (OverflowException) | ||
| 159 | { | ||
| 160 | throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); | ||
| 161 | } | ||
| 162 | |||
| 163 | var mediaSymbolsByDiskId = new Dictionary<int, MediaSymbol>(); | ||
| 164 | |||
| 165 | foreach (var facade in this.FileFacades) | ||
| 166 | { | ||
| 167 | // When building a product, if the current file is not to be compressed or if | ||
| 168 | // the package set not to be compressed, don't cab it. | ||
| 169 | if (SectionType.Product == this.Section.Type && (facade.Uncompressed || !this.FilesCompressed)) | ||
| 170 | { | ||
| 171 | uncompressedFiles.Add(facade); | ||
| 172 | continue; | ||
| 173 | } | ||
| 174 | |||
| 175 | if (currentCabIndex == MaxCabIndex) | ||
| 176 | { | ||
| 177 | // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. | ||
| 178 | } | ||
| 179 | else | ||
| 180 | { | ||
| 181 | // Update current cab size. | ||
| 182 | currentPreCabSize += (ulong)facade.FileSize; | ||
| 183 | |||
| 184 | // Overflow due to current file | ||
| 185 | if (currentPreCabSize > maxPreCabSizeInBytes) | ||
| 186 | { | ||
| 187 | currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); | ||
| 188 | mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); | ||
| 189 | filesByCabinetMedia.Add(currentMediaRow, new List<IFileFacade>()); | ||
| 190 | |||
| 191 | // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize | ||
| 192 | currentPreCabSize = (ulong)facade.FileSize; | ||
| 193 | } | ||
| 194 | else // file fits in the current cab. | ||
| 195 | { | ||
| 196 | if (currentMediaRow == null) | ||
| 197 | { | ||
| 198 | // Create new cab and MediaRow | ||
| 199 | currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); | ||
| 200 | mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); | ||
| 201 | filesByCabinetMedia.Add(currentMediaRow, new List<IFileFacade>()); | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | // Associate current file with current cab. | ||
| 207 | var cabinetFiles = filesByCabinetMedia[currentMediaRow]; | ||
| 208 | facade.DiskId = currentCabIndex; | ||
| 209 | cabinetFiles.Add(facade); | ||
| 210 | } | ||
| 211 | |||
| 212 | // If there are uncompressed files and no MediaRow, create a default one. | ||
| 213 | if (uncompressedFiles.Count > 0 && mediaSymbolsByDiskId.Count == 0) | ||
| 214 | { | ||
| 215 | var defaultMediaRow = this.Section.AddSymbol(new MediaSymbol(null, new Identifier(AccessModifier.Section, 1)) | ||
| 216 | { | ||
| 217 | DiskId = 1, | ||
| 218 | }); | ||
| 219 | |||
| 220 | mediaSymbolsByDiskId.Add(1, defaultMediaRow); | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | /// <summary> | ||
| 225 | /// Assign files to cabinets based on Media authoring. | ||
| 226 | /// </summary> | ||
| 227 | private void ManuallyAssignFiles(List<MediaSymbol> mediaSymbols, Dictionary<MediaSymbol, List<IFileFacade>> filesByCabinetMedia, List<IFileFacade> uncompressedFiles) | ||
| 228 | { | ||
| 229 | var mediaSymbolsByDiskId = new Dictionary<int, MediaSymbol>(); | ||
| 230 | |||
| 231 | if (mediaSymbols.Any()) | ||
| 232 | { | ||
| 233 | var cabinetMediaSymbols = new Dictionary<string, MediaSymbol>(StringComparer.OrdinalIgnoreCase); | ||
| 234 | foreach (var mediaSymbol in mediaSymbols) | ||
| 235 | { | ||
| 236 | // If the Media row has a cabinet, make sure it is unique across all Media rows. | ||
| 237 | if (!String.IsNullOrEmpty(mediaSymbol.Cabinet)) | ||
| 238 | { | ||
| 239 | if (cabinetMediaSymbols.TryGetValue(mediaSymbol.Cabinet, out var existingRow)) | ||
| 240 | { | ||
| 241 | this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet)); | ||
| 242 | this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); | ||
| 243 | } | ||
| 244 | else | ||
| 245 | { | ||
| 246 | cabinetMediaSymbols.Add(mediaSymbol.Cabinet, mediaSymbol); | ||
| 247 | } | ||
| 248 | |||
| 249 | filesByCabinetMedia.Add(mediaSymbol, new List<IFileFacade>()); | ||
| 250 | } | ||
| 251 | |||
| 252 | mediaSymbolsByDiskId.Add(mediaSymbol.DiskId, mediaSymbol); | ||
| 253 | } | ||
| 254 | } | ||
| 255 | |||
| 256 | foreach (var facade in this.FileFacades) | ||
| 257 | { | ||
| 258 | if (!mediaSymbolsByDiskId.TryGetValue(facade.DiskId, out var mediaSymbol)) | ||
| 259 | { | ||
| 260 | this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId)); | ||
| 261 | continue; | ||
| 262 | } | ||
| 263 | |||
| 264 | // When building a product, if the current file is to be uncompressed or if | ||
| 265 | // the package set not to be compressed, don't cab it. | ||
| 266 | var compressed = facade.Compressed; | ||
| 267 | var uncompressed = facade.Uncompressed; | ||
| 268 | if (SectionType.Product == this.Section.Type && (uncompressed || (!compressed && !this.FilesCompressed))) | ||
| 269 | { | ||
| 270 | uncompressedFiles.Add(facade); | ||
| 271 | } | ||
| 272 | else // file is marked compressed. | ||
| 273 | { | ||
| 274 | if (filesByCabinetMedia.TryGetValue(mediaSymbol, out var cabinetFiles)) | ||
| 275 | { | ||
| 276 | cabinetFiles.Add(facade); | ||
| 277 | } | ||
| 278 | else | ||
| 279 | { | ||
| 280 | this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.SourceLineNumber, facade.Id, facade.DiskId)); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | } | ||
| 284 | } | ||
| 285 | |||
| 286 | /// <summary> | ||
| 287 | /// Adds a symbol to the section with cab name template filled in. | ||
| 288 | /// </summary> | ||
| 289 | /// <param name="mediaTemplateSymbol"></param> | ||
| 290 | /// <param name="cabIndex"></param> | ||
| 291 | /// <returns></returns> | ||
| 292 | private MediaSymbol AddMediaSymbol(WixMediaTemplateSymbol mediaTemplateSymbol, int cabIndex) | ||
| 293 | { | ||
| 294 | return this.Section.AddSymbol(new MediaSymbol(mediaTemplateSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, cabIndex)) | ||
| 295 | { | ||
| 296 | DiskId = cabIndex, | ||
| 297 | Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex), | ||
| 298 | CompressionLevel = mediaTemplateSymbol.CompressionLevel, | ||
| 299 | }); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs new file mode 100644 index 00000000..76bcd532 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs | |||
| @@ -0,0 +1,1305 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Text.RegularExpressions; | ||
| 10 | using WixToolset.Core.Native.Msi; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Data.WindowsInstaller; | ||
| 14 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 15 | using WixToolset.Extensibility.Services; | ||
| 16 | |||
| 17 | /// <summary> | ||
| 18 | /// Include transforms in a patch. | ||
| 19 | /// </summary> | ||
| 20 | internal class AttachPatchTransformsCommand | ||
| 21 | { | ||
| 22 | private static readonly string[] PatchUninstallBreakingTables = new[] | ||
| 23 | { | ||
| 24 | "AppId", | ||
| 25 | "BindImage", | ||
| 26 | "Class", | ||
| 27 | "Complus", | ||
| 28 | "CreateFolder", | ||
| 29 | "DuplicateFile", | ||
| 30 | "Environment", | ||
| 31 | "Extension", | ||
| 32 | "Font", | ||
| 33 | "IniFile", | ||
| 34 | "IsolatedComponent", | ||
| 35 | "LockPermissions", | ||
| 36 | "MIME", | ||
| 37 | "MoveFile", | ||
| 38 | "MsiLockPermissionsEx", | ||
| 39 | "MsiServiceConfig", | ||
| 40 | "MsiServiceConfigFailureActions", | ||
| 41 | "ODBCAttribute", | ||
| 42 | "ODBCDataSource", | ||
| 43 | "ODBCDriver", | ||
| 44 | "ODBCSourceAttribute", | ||
| 45 | "ODBCTranslator", | ||
| 46 | "ProgId", | ||
| 47 | "PublishComponent", | ||
| 48 | "RemoveIniFile", | ||
| 49 | "SelfReg", | ||
| 50 | "ServiceControl", | ||
| 51 | "ServiceInstall", | ||
| 52 | "TypeLib", | ||
| 53 | "Verb", | ||
| 54 | }; | ||
| 55 | |||
| 56 | private readonly TableDefinitionCollection tableDefinitions; | ||
| 57 | |||
| 58 | public AttachPatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, IEnumerable<PatchTransform> transforms) | ||
| 59 | { | ||
| 60 | this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); | ||
| 61 | this.Messaging = messaging; | ||
| 62 | this.BackendHelper = backendHelper; | ||
| 63 | this.Intermediate = intermediate; | ||
| 64 | this.Transforms = transforms; | ||
| 65 | } | ||
| 66 | |||
| 67 | private IMessaging Messaging { get; } | ||
| 68 | |||
| 69 | private IBackendHelper BackendHelper { get; } | ||
| 70 | |||
| 71 | private Intermediate Intermediate { get; } | ||
| 72 | |||
| 73 | private IEnumerable<PatchTransform> Transforms { get; } | ||
| 74 | |||
| 75 | public IEnumerable<SubStorage> SubStorages { get; private set; } | ||
| 76 | |||
| 77 | public IEnumerable<SubStorage> Execute() | ||
| 78 | { | ||
| 79 | var subStorages = new List<SubStorage>(); | ||
| 80 | |||
| 81 | if (this.Transforms == null || !this.Transforms.Any()) | ||
| 82 | { | ||
| 83 | this.Messaging.Write(ErrorMessages.PatchWithoutTransforms()); | ||
| 84 | return subStorages; | ||
| 85 | } | ||
| 86 | |||
| 87 | var summaryInfo = this.ExtractPatchSummaryInfo(); | ||
| 88 | |||
| 89 | var section = this.Intermediate.Sections.First(); | ||
| 90 | |||
| 91 | var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList(); | ||
| 92 | |||
| 93 | // Get the patch id from the WixPatchId symbol. | ||
| 94 | var patchSymbol = symbols.OfType<WixPatchSymbol>().FirstOrDefault(); | ||
| 95 | |||
| 96 | if (String.IsNullOrEmpty(patchSymbol.Id?.Id)) | ||
| 97 | { | ||
| 98 | this.Messaging.Write(ErrorMessages.ExpectedPatchIdInWixMsp()); | ||
| 99 | return subStorages; | ||
| 100 | } | ||
| 101 | |||
| 102 | if (String.IsNullOrEmpty(patchSymbol.ClientPatchId)) | ||
| 103 | { | ||
| 104 | this.Messaging.Write(ErrorMessages.ExpectedClientPatchIdInWixMsp()); | ||
| 105 | return subStorages; | ||
| 106 | } | ||
| 107 | |||
| 108 | // enumerate patch.Media to map diskId to Media row | ||
| 109 | var patchMediaByDiskId = symbols.OfType<MediaSymbol>().ToDictionary(t => t.DiskId); | ||
| 110 | |||
| 111 | if (patchMediaByDiskId.Count == 0) | ||
| 112 | { | ||
| 113 | this.Messaging.Write(ErrorMessages.ExpectedMediaRowsInWixMsp()); | ||
| 114 | return subStorages; | ||
| 115 | } | ||
| 116 | |||
| 117 | // populate MSP summary information | ||
| 118 | var patchMetadata = this.PopulateSummaryInformation(summaryInfo, symbols, patchSymbol); | ||
| 119 | |||
| 120 | // enumerate transforms | ||
| 121 | var productCodes = new SortedSet<string>(); | ||
| 122 | var transformNames = new List<string>(); | ||
| 123 | var validTransform = new List<Tuple<string, WindowsInstallerData>>(); | ||
| 124 | |||
| 125 | var baselineSymbolsById = symbols.OfType<WixPatchBaselineSymbol>().ToDictionary(t => t.Id.Id); | ||
| 126 | |||
| 127 | foreach (var mainTransform in this.Transforms) | ||
| 128 | { | ||
| 129 | var baselineSymbol = baselineSymbolsById[mainTransform.Baseline]; | ||
| 130 | |||
| 131 | var patchRefSymbols = symbols.OfType<WixPatchRefSymbol>().ToList(); | ||
| 132 | if (patchRefSymbols.Count > 0) | ||
| 133 | { | ||
| 134 | if (!this.ReduceTransform(mainTransform.Transform, patchRefSymbols)) | ||
| 135 | { | ||
| 136 | // transform has none of the content authored into this patch | ||
| 137 | continue; | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | // Validate the transform doesn't break any patch specific rules. | ||
| 142 | this.Validate(mainTransform); | ||
| 143 | |||
| 144 | // ensure consistent File.Sequence within each Media | ||
| 145 | var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId]; | ||
| 146 | |||
| 147 | // Ensure that files are sequenced after the last file in any transform. | ||
| 148 | var transformMediaTable = mainTransform.Transform.Tables["Media"]; | ||
| 149 | if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) | ||
| 150 | { | ||
| 151 | foreach (MediaRow transformMediaRow in transformMediaTable.Rows) | ||
| 152 | { | ||
| 153 | if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < transformMediaRow.LastSequence) | ||
| 154 | { | ||
| 155 | // The Binder will pre-increment the sequence. | ||
| 156 | mediaSymbol.LastSequence = transformMediaRow.LastSequence; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | // Use the Media/@DiskId if greater than the last sequence for backward compatibility. | ||
| 162 | if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < mediaSymbol.DiskId) | ||
| 163 | { | ||
| 164 | mediaSymbol.LastSequence = mediaSymbol.DiskId; | ||
| 165 | } | ||
| 166 | |||
| 167 | // Ignore media table in the transform. | ||
| 168 | mainTransform.Transform.Tables.Remove("Media"); | ||
| 169 | mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); | ||
| 170 | |||
| 171 | var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchSymbol, mainTransform.Transform, mediaSymbol, baselineSymbol, out var productCode); | ||
| 172 | |||
| 173 | productCode = productCode.ToUpperInvariant(); | ||
| 174 | productCodes.Add(productCode); | ||
| 175 | validTransform.Add(Tuple.Create(productCode, mainTransform.Transform)); | ||
| 176 | |||
| 177 | // attach these transforms to the patch object | ||
| 178 | // TODO: is this an acceptable way to auto-generate transform stream names? | ||
| 179 | var transformName = mainTransform.Baseline + "." + validTransform.Count.ToString(CultureInfo.InvariantCulture); | ||
| 180 | subStorages.Add(new SubStorage(transformName, mainTransform.Transform)); | ||
| 181 | subStorages.Add(new SubStorage("#" + transformName, pairedTransform)); | ||
| 182 | |||
| 183 | transformNames.Add(":" + transformName); | ||
| 184 | transformNames.Add(":#" + transformName); | ||
| 185 | } | ||
| 186 | |||
| 187 | if (validTransform.Count == 0) | ||
| 188 | { | ||
| 189 | this.Messaging.Write(ErrorMessages.PatchWithoutValidTransforms()); | ||
| 190 | return subStorages; | ||
| 191 | } | ||
| 192 | |||
| 193 | // Validate that a patch authored as removable is actually removable | ||
| 194 | if (patchMetadata.TryGetValue("AllowRemoval", out var allowRemoval) && allowRemoval.Value == "1") | ||
| 195 | { | ||
| 196 | var uninstallable = true; | ||
| 197 | |||
| 198 | foreach (var entry in validTransform) | ||
| 199 | { | ||
| 200 | uninstallable &= this.CheckUninstallableTransform(entry.Item1, entry.Item2); | ||
| 201 | } | ||
| 202 | |||
| 203 | if (!uninstallable) | ||
| 204 | { | ||
| 205 | this.Messaging.Write(ErrorMessages.PatchNotRemovable()); | ||
| 206 | return subStorages; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | // Finish filling tables with transform-dependent data. | ||
| 211 | productCodes = FinalizePatchProductCodes(symbols, productCodes); | ||
| 212 | |||
| 213 | // Semicolon delimited list of the product codes that can accept the patch. | ||
| 214 | summaryInfo.Add(SummaryInformationType.PatchProductCodes, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) | ||
| 215 | { | ||
| 216 | PropertyId = SummaryInformationType.PatchProductCodes, | ||
| 217 | Value = String.Join(";", productCodes) | ||
| 218 | }); | ||
| 219 | |||
| 220 | // Semicolon delimited list of transform substorage names in the order they are applied. | ||
| 221 | summaryInfo.Add(SummaryInformationType.TransformNames, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) | ||
| 222 | { | ||
| 223 | PropertyId = SummaryInformationType.TransformNames, | ||
| 224 | Value = String.Join(";", transformNames) | ||
| 225 | }); | ||
| 226 | |||
| 227 | // Put the summary information that was extracted back in now that it is updated. | ||
| 228 | foreach (var readSummaryInfo in summaryInfo.Values.OrderBy(s => s.PropertyId)) | ||
| 229 | { | ||
| 230 | section.AddSymbol(readSummaryInfo); | ||
| 231 | } | ||
| 232 | |||
| 233 | this.SubStorages = subStorages; | ||
| 234 | |||
| 235 | return subStorages; | ||
| 236 | } | ||
| 237 | |||
| 238 | private Dictionary<SummaryInformationType, SummaryInformationSymbol> ExtractPatchSummaryInfo() | ||
| 239 | { | ||
| 240 | var result = new Dictionary<SummaryInformationType, SummaryInformationSymbol>(); | ||
| 241 | |||
| 242 | foreach (var section in this.Intermediate.Sections) | ||
| 243 | { | ||
| 244 | // Remove all summary information from the symbols and remember those that | ||
| 245 | // are not calculated or reserved. | ||
| 246 | foreach (var patchSummaryInfo in section.Symbols.OfType<SummaryInformationSymbol>().ToList()) | ||
| 247 | { | ||
| 248 | section.RemoveSymbol(patchSummaryInfo); | ||
| 249 | |||
| 250 | if (patchSummaryInfo.PropertyId != SummaryInformationType.PatchProductCodes && | ||
| 251 | patchSummaryInfo.PropertyId != SummaryInformationType.PatchCode && | ||
| 252 | patchSummaryInfo.PropertyId != SummaryInformationType.PatchInstallerRequirement && | ||
| 253 | patchSummaryInfo.PropertyId != SummaryInformationType.Reserved11 && | ||
| 254 | patchSummaryInfo.PropertyId != SummaryInformationType.Reserved14 && | ||
| 255 | patchSummaryInfo.PropertyId != SummaryInformationType.Reserved16) | ||
| 256 | { | ||
| 257 | result.Add(patchSummaryInfo.PropertyId, patchSummaryInfo); | ||
| 258 | } | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | return result; | ||
| 263 | } | ||
| 264 | |||
| 265 | private Dictionary<string, MsiPatchMetadataSymbol> PopulateSummaryInformation(Dictionary<SummaryInformationType, SummaryInformationSymbol> summaryInfo, List<IntermediateSymbol> symbols, WixPatchSymbol patchSymbol) | ||
| 266 | { | ||
| 267 | // PID_CODEPAGE | ||
| 268 | if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage)) | ||
| 269 | { | ||
| 270 | // Set the code page by default to the same code page for the | ||
| 271 | // string pool in the database. | ||
| 272 | AddSummaryInformation(SummaryInformationType.Codepage, patchSymbol.Codepage?.ToString(CultureInfo.InvariantCulture) ?? "0", patchSymbol.SourceLineNumbers); | ||
| 273 | } | ||
| 274 | |||
| 275 | // GUID patch code for the patch. | ||
| 276 | AddSummaryInformation(SummaryInformationType.PatchCode, patchSymbol.Id.Id, patchSymbol.SourceLineNumbers); | ||
| 277 | |||
| 278 | // Indicates the minimum Windows Installer version that is required to install the patch. | ||
| 279 | AddSummaryInformation(SummaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchSymbol.SourceLineNumbers); | ||
| 280 | |||
| 281 | if (!summaryInfo.ContainsKey(SummaryInformationType.Security)) | ||
| 282 | { | ||
| 283 | AddSummaryInformation(SummaryInformationType.Security, "4", patchSymbol.SourceLineNumbers); // Read-only enforced; | ||
| 284 | } | ||
| 285 | |||
| 286 | // Use authored comments or default to display name. | ||
| 287 | MsiPatchMetadataSymbol commentsSymbol = null; | ||
| 288 | |||
| 289 | var metadataSymbols = symbols.OfType<MsiPatchMetadataSymbol>().Where(t => String.IsNullOrEmpty(t.Company)).ToDictionary(t => t.Property); | ||
| 290 | |||
| 291 | if (!summaryInfo.ContainsKey(SummaryInformationType.Title) && | ||
| 292 | metadataSymbols.TryGetValue("DisplayName", out var displayName)) | ||
| 293 | { | ||
| 294 | AddSummaryInformation(SummaryInformationType.Title, displayName.Value, displayName.SourceLineNumbers); | ||
| 295 | |||
| 296 | // Default comments to use display name as-is. | ||
| 297 | commentsSymbol = displayName; | ||
| 298 | } | ||
| 299 | |||
| 300 | // TODO: This code below seems unnecessary given the codepage is set at the top of this method. | ||
| 301 | //if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage) && | ||
| 302 | // metadataValues.TryGetValue("CodePage", out var codepage)) | ||
| 303 | //{ | ||
| 304 | // AddSummaryInformation(SummaryInformationType.Codepage, codepage); | ||
| 305 | //} | ||
| 306 | |||
| 307 | if (!summaryInfo.ContainsKey(SummaryInformationType.PatchPackageName) && | ||
| 308 | metadataSymbols.TryGetValue("Description", out var description)) | ||
| 309 | { | ||
| 310 | AddSummaryInformation(SummaryInformationType.PatchPackageName, description.Value, description.SourceLineNumbers); | ||
| 311 | } | ||
| 312 | |||
| 313 | if (!summaryInfo.ContainsKey(SummaryInformationType.Author) && | ||
| 314 | metadataSymbols.TryGetValue("ManufacturerName", out var manufacturer)) | ||
| 315 | { | ||
| 316 | AddSummaryInformation(SummaryInformationType.Author, manufacturer.Value, manufacturer.SourceLineNumbers); | ||
| 317 | } | ||
| 318 | |||
| 319 | // Special metadata marshalled through the build. | ||
| 320 | //var wixMetadataValues = symbols.OfType<WixPatchMetadataSymbol>().ToDictionary(t => t.Id.Id, t => t.Value); | ||
| 321 | |||
| 322 | //if (wixMetadataValues.TryGetValue("Comments", out var wixComments)) | ||
| 323 | if (metadataSymbols.TryGetValue("Comments", out var wixComments)) | ||
| 324 | { | ||
| 325 | commentsSymbol = wixComments; | ||
| 326 | } | ||
| 327 | |||
| 328 | // Write the package comments to summary info. | ||
| 329 | if (!summaryInfo.ContainsKey(SummaryInformationType.Comments) && | ||
| 330 | commentsSymbol != null) | ||
| 331 | { | ||
| 332 | AddSummaryInformation(SummaryInformationType.Comments, commentsSymbol.Value, commentsSymbol.SourceLineNumbers); | ||
| 333 | } | ||
| 334 | |||
| 335 | return metadataSymbols; | ||
| 336 | |||
| 337 | void AddSummaryInformation(SummaryInformationType type, string value, SourceLineNumber sourceLineNumber) | ||
| 338 | { | ||
| 339 | summaryInfo.Add(type, new SummaryInformationSymbol(sourceLineNumber) | ||
| 340 | { | ||
| 341 | PropertyId = type, | ||
| 342 | Value = value | ||
| 343 | }); | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | /// <summary> | ||
| 348 | /// Ensure transform is uninstallable. | ||
| 349 | /// </summary> | ||
| 350 | /// <param name="productCode">Product code in transform.</param> | ||
| 351 | /// <param name="transform">Transform generated by torch.</param> | ||
| 352 | /// <returns>True if the transform is uninstallable</returns> | ||
| 353 | private bool CheckUninstallableTransform(string productCode, WindowsInstallerData transform) | ||
| 354 | { | ||
| 355 | var success = true; | ||
| 356 | |||
| 357 | foreach (var tableName in PatchUninstallBreakingTables) | ||
| 358 | { | ||
| 359 | if (transform.TryGetTable(tableName, out var table)) | ||
| 360 | { | ||
| 361 | foreach (var row in table.Rows) | ||
| 362 | { | ||
| 363 | if (row.Operation == RowOperation.Add) | ||
| 364 | { | ||
| 365 | success = false; | ||
| 366 | |||
| 367 | var primaryKey = row.GetPrimaryKey('/') ?? String.Empty; | ||
| 368 | |||
| 369 | this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey)); | ||
| 370 | } | ||
| 371 | } | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | return success; | ||
| 376 | } | ||
| 377 | |||
| 378 | /// <summary> | ||
| 379 | /// Reduce the transform according to the patch references. | ||
| 380 | /// </summary> | ||
| 381 | /// <param name="transform">transform generated by torch.</param> | ||
| 382 | /// <param name="patchRefSymbols">Table contains patch family filter.</param> | ||
| 383 | /// <returns>true if the transform is not empty</returns> | ||
| 384 | private bool ReduceTransform(WindowsInstallerData transform, IEnumerable<WixPatchRefSymbol> patchRefSymbols) | ||
| 385 | { | ||
| 386 | // identify sections to keep | ||
| 387 | var oldSections = new Dictionary<string, Row>(); | ||
| 388 | var newSections = new Dictionary<string, Row>(); | ||
| 389 | var tableKeyRows = new Dictionary<string, Dictionary<string, Row>>(); | ||
| 390 | var sequenceList = new List<Table>(); | ||
| 391 | var componentFeatureAddsIndex = new Dictionary<string, List<string>>(); | ||
| 392 | var customActionTable = new Dictionary<string, Row>(); | ||
| 393 | var directoryTableAdds = new Dictionary<string, Row>(); | ||
| 394 | var featureTableAdds = new Dictionary<string, Row>(); | ||
| 395 | var keptComponents = new Dictionary<string, Row>(); | ||
| 396 | var keptDirectories = new Dictionary<string, Row>(); | ||
| 397 | var keptFeatures = new Dictionary<string, Row>(); | ||
| 398 | var keptLockPermissions = new HashSet<string>(); | ||
| 399 | var keptMsiLockPermissionExs = new HashSet<string>(); | ||
| 400 | |||
| 401 | var componentCreateFolderIndex = new Dictionary<string, List<string>>(); | ||
| 402 | var directoryLockPermissionsIndex = new Dictionary<string, List<Row>>(); | ||
| 403 | var directoryMsiLockPermissionsExIndex = new Dictionary<string, List<Row>>(); | ||
| 404 | |||
| 405 | foreach (var patchRefSymbol in patchRefSymbols) | ||
| 406 | { | ||
| 407 | var tableName = patchRefSymbol.Table; | ||
| 408 | var key = patchRefSymbol.PrimaryKeys; | ||
| 409 | |||
| 410 | // Short circuit filtering if all changes should be included. | ||
| 411 | if ("*" == tableName && "*" == key) | ||
| 412 | { | ||
| 413 | RemoveProductCodeFromTransform(transform); | ||
| 414 | return true; | ||
| 415 | } | ||
| 416 | |||
| 417 | if (!transform.Tables.TryGetTable(tableName, out var table)) | ||
| 418 | { | ||
| 419 | // Table not found. | ||
| 420 | continue; | ||
| 421 | } | ||
| 422 | |||
| 423 | // Index the table. | ||
| 424 | if (!tableKeyRows.TryGetValue(tableName, out var keyRows)) | ||
| 425 | { | ||
| 426 | keyRows = new Dictionary<string, Row>(); | ||
| 427 | tableKeyRows.Add(tableName, keyRows); | ||
| 428 | |||
| 429 | foreach (var newRow in table.Rows) | ||
| 430 | { | ||
| 431 | var primaryKey = newRow.GetPrimaryKey(); | ||
| 432 | keyRows.Add(primaryKey, newRow); | ||
| 433 | } | ||
| 434 | } | ||
| 435 | |||
| 436 | if (!keyRows.TryGetValue(key, out var row)) | ||
| 437 | { | ||
| 438 | // Row not found. | ||
| 439 | continue; | ||
| 440 | } | ||
| 441 | |||
| 442 | // Differ.sectionDelimiter | ||
| 443 | var sections = row.SectionId.Split('/'); | ||
| 444 | oldSections[sections[0]] = row; | ||
| 445 | newSections[sections[1]] = row; | ||
| 446 | } | ||
| 447 | |||
| 448 | // throw away sections not referenced | ||
| 449 | var keptRows = 0; | ||
| 450 | Table directoryTable = null; | ||
| 451 | Table featureTable = null; | ||
| 452 | Table lockPermissionsTable = null; | ||
| 453 | Table msiLockPermissionsTable = null; | ||
| 454 | |||
| 455 | foreach (var table in transform.Tables) | ||
| 456 | { | ||
| 457 | if ("_SummaryInformation" == table.Name) | ||
| 458 | { | ||
| 459 | continue; | ||
| 460 | } | ||
| 461 | |||
| 462 | if (table.Name == "AdminExecuteSequence" | ||
| 463 | || table.Name == "AdminUISequence" | ||
| 464 | || table.Name == "AdvtExecuteSequence" | ||
| 465 | || table.Name == "InstallUISequence" | ||
| 466 | || table.Name == "InstallExecuteSequence") | ||
| 467 | { | ||
| 468 | sequenceList.Add(table); | ||
| 469 | continue; | ||
| 470 | } | ||
| 471 | |||
| 472 | for (var i = 0; i < table.Rows.Count; i++) | ||
| 473 | { | ||
| 474 | var row = table.Rows[i]; | ||
| 475 | |||
| 476 | if (table.Name == "CreateFolder") | ||
| 477 | { | ||
| 478 | var createFolderComponentId = row.FieldAsString(1); | ||
| 479 | |||
| 480 | if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList)) | ||
| 481 | { | ||
| 482 | directoryList = new List<string>(); | ||
| 483 | componentCreateFolderIndex.Add(createFolderComponentId, directoryList); | ||
| 484 | } | ||
| 485 | |||
| 486 | directoryList.Add(row.FieldAsString(0)); | ||
| 487 | } | ||
| 488 | |||
| 489 | if (table.Name == "CustomAction") | ||
| 490 | { | ||
| 491 | customActionTable.Add(row.FieldAsString(0), row); | ||
| 492 | } | ||
| 493 | |||
| 494 | if (table.Name == "Directory") | ||
| 495 | { | ||
| 496 | directoryTable = table; | ||
| 497 | if (RowOperation.Add == row.Operation) | ||
| 498 | { | ||
| 499 | directoryTableAdds.Add(row.FieldAsString(0), row); | ||
| 500 | } | ||
| 501 | } | ||
| 502 | |||
| 503 | if (table.Name == "Feature") | ||
| 504 | { | ||
| 505 | featureTable = table; | ||
| 506 | if (RowOperation.Add == row.Operation) | ||
| 507 | { | ||
| 508 | featureTableAdds.Add(row.FieldAsString(0), row); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | if (table.Name == "FeatureComponents") | ||
| 513 | { | ||
| 514 | if (RowOperation.Add == row.Operation) | ||
| 515 | { | ||
| 516 | var featureId = row.FieldAsString(0); | ||
| 517 | var componentId = row.FieldAsString(1); | ||
| 518 | |||
| 519 | if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList)) | ||
| 520 | { | ||
| 521 | featureList = new List<string>(); | ||
| 522 | componentFeatureAddsIndex.Add(componentId, featureList); | ||
| 523 | } | ||
| 524 | |||
| 525 | featureList.Add(featureId); | ||
| 526 | } | ||
| 527 | } | ||
| 528 | |||
| 529 | if (table.Name == "LockPermissions") | ||
| 530 | { | ||
| 531 | lockPermissionsTable = table; | ||
| 532 | if ("CreateFolder" == row.FieldAsString(1)) | ||
| 533 | { | ||
| 534 | var directoryId = row.FieldAsString(0); | ||
| 535 | |||
| 536 | if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList)) | ||
| 537 | { | ||
| 538 | rowList = new List<Row>(); | ||
| 539 | directoryLockPermissionsIndex.Add(directoryId, rowList); | ||
| 540 | } | ||
| 541 | |||
| 542 | rowList.Add(row); | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | if (table.Name == "MsiLockPermissionsEx") | ||
| 547 | { | ||
| 548 | msiLockPermissionsTable = table; | ||
| 549 | if ("CreateFolder" == row.FieldAsString(1)) | ||
| 550 | { | ||
| 551 | var directoryId = row.FieldAsString(0); | ||
| 552 | |||
| 553 | if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList)) | ||
| 554 | { | ||
| 555 | rowList = new List<Row>(); | ||
| 556 | directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); | ||
| 557 | } | ||
| 558 | |||
| 559 | rowList.Add(row); | ||
| 560 | } | ||
| 561 | } | ||
| 562 | |||
| 563 | if (null == row.SectionId) | ||
| 564 | { | ||
| 565 | table.Rows.RemoveAt(i); | ||
| 566 | i--; | ||
| 567 | } | ||
| 568 | else | ||
| 569 | { | ||
| 570 | var sections = row.SectionId.Split('/'); | ||
| 571 | // ignore the row without section id. | ||
| 572 | if (0 == sections[0].Length && 0 == sections[1].Length) | ||
| 573 | { | ||
| 574 | table.Rows.RemoveAt(i); | ||
| 575 | i--; | ||
| 576 | } | ||
| 577 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
| 578 | { | ||
| 579 | if ("Component" == table.Name) | ||
| 580 | { | ||
| 581 | keptComponents.Add(row.FieldAsString(0), row); | ||
| 582 | } | ||
| 583 | |||
| 584 | if ("Directory" == table.Name) | ||
| 585 | { | ||
| 586 | keptDirectories.Add(row.FieldAsString(0), row); | ||
| 587 | } | ||
| 588 | |||
| 589 | if ("Feature" == table.Name) | ||
| 590 | { | ||
| 591 | keptFeatures.Add(row.FieldAsString(0), row); | ||
| 592 | } | ||
| 593 | |||
| 594 | keptRows++; | ||
| 595 | } | ||
| 596 | else | ||
| 597 | { | ||
| 598 | table.Rows.RemoveAt(i); | ||
| 599 | i--; | ||
| 600 | } | ||
| 601 | } | ||
| 602 | } | ||
| 603 | } | ||
| 604 | |||
| 605 | keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); | ||
| 606 | |||
| 607 | if (null != directoryTable) | ||
| 608 | { | ||
| 609 | foreach (var componentRow in keptComponents.Values) | ||
| 610 | { | ||
| 611 | var componentId = componentRow.FieldAsString(0); | ||
| 612 | |||
| 613 | if (RowOperation.Add == componentRow.Operation) | ||
| 614 | { | ||
| 615 | // Make sure each added component has its required directory and feature heirarchy. | ||
| 616 | var directoryId = componentRow.FieldAsString(2); | ||
| 617 | while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow)) | ||
| 618 | { | ||
| 619 | if (!keptDirectories.ContainsKey(directoryId)) | ||
| 620 | { | ||
| 621 | directoryTable.Rows.Add(directoryRow); | ||
| 622 | keptDirectories.Add(directoryId, directoryRow); | ||
| 623 | keptRows++; | ||
| 624 | } | ||
| 625 | |||
| 626 | directoryId = directoryRow.FieldAsString(1); | ||
| 627 | } | ||
| 628 | |||
| 629 | if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds)) | ||
| 630 | { | ||
| 631 | foreach (var featureId in componentFeatureIds) | ||
| 632 | { | ||
| 633 | var currentFeatureId = featureId; | ||
| 634 | while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow)) | ||
| 635 | { | ||
| 636 | if (!keptFeatures.ContainsKey(currentFeatureId)) | ||
| 637 | { | ||
| 638 | featureTable.Rows.Add(featureRow); | ||
| 639 | keptFeatures.Add(currentFeatureId, featureRow); | ||
| 640 | keptRows++; | ||
| 641 | } | ||
| 642 | |||
| 643 | currentFeatureId = featureRow.FieldAsString(1); | ||
| 644 | } | ||
| 645 | } | ||
| 646 | } | ||
| 647 | } | ||
| 648 | |||
| 649 | // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. | ||
| 650 | foreach (var keptComponentId in keptComponents.Keys) | ||
| 651 | { | ||
| 652 | if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList)) | ||
| 653 | { | ||
| 654 | foreach (var directoryId in directoryList) | ||
| 655 | { | ||
| 656 | if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList)) | ||
| 657 | { | ||
| 658 | foreach (var lockPermissionsRow in lockPermissionsRowList) | ||
| 659 | { | ||
| 660 | var key = lockPermissionsRow.GetPrimaryKey('/'); | ||
| 661 | if (keptLockPermissions.Add(key)) | ||
| 662 | { | ||
| 663 | lockPermissionsTable.Rows.Add(lockPermissionsRow); | ||
| 664 | keptRows++; | ||
| 665 | } | ||
| 666 | } | ||
| 667 | } | ||
| 668 | |||
| 669 | if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList)) | ||
| 670 | { | ||
| 671 | foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList) | ||
| 672 | { | ||
| 673 | var key = msiLockPermissionsExRow.GetPrimaryKey('/'); | ||
| 674 | if (keptMsiLockPermissionExs.Add(key)) | ||
| 675 | { | ||
| 676 | msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); | ||
| 677 | keptRows++; | ||
| 678 | } | ||
| 679 | } | ||
| 680 | } | ||
| 681 | } | ||
| 682 | } | ||
| 683 | } | ||
| 684 | } | ||
| 685 | } | ||
| 686 | |||
| 687 | keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); | ||
| 688 | |||
| 689 | // Delete tables that are empty. | ||
| 690 | var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList(); | ||
| 691 | |||
| 692 | foreach (var tableName in tablesToDelete) | ||
| 693 | { | ||
| 694 | transform.Tables.Remove(tableName); | ||
| 695 | } | ||
| 696 | |||
| 697 | return keptRows > 0; | ||
| 698 | } | ||
| 699 | |||
| 700 | private void Validate(PatchTransform patchTransform) | ||
| 701 | { | ||
| 702 | var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it? | ||
| 703 | var transform = patchTransform.Transform; | ||
| 704 | |||
| 705 | // Changing the ProdocutCode in a patch transform is not recommended. | ||
| 706 | if (transform.TryGetTable("Property", out var propertyTable)) | ||
| 707 | { | ||
| 708 | foreach (var row in propertyTable.Rows) | ||
| 709 | { | ||
| 710 | // Only interested in modified rows; fast check. | ||
| 711 | if (RowOperation.Modify == row.Operation && | ||
| 712 | "ProductCode".Equals(row.FieldAsString(0), StringComparison.Ordinal)) | ||
| 713 | { | ||
| 714 | this.Messaging.Write(WarningMessages.MajorUpgradePatchNotRecommended()); | ||
| 715 | } | ||
| 716 | } | ||
| 717 | } | ||
| 718 | |||
| 719 | // If there is nothing in the component table we can return early because the remaining checks are component based. | ||
| 720 | if (!transform.TryGetTable("Component", out var componentTable)) | ||
| 721 | { | ||
| 722 | return; | ||
| 723 | } | ||
| 724 | |||
| 725 | // Index Feature table row operations | ||
| 726 | var featureOps = new Dictionary<string, RowOperation>(); | ||
| 727 | if (transform.TryGetTable("Feature", out var featureTable)) | ||
| 728 | { | ||
| 729 | foreach (var row in featureTable.Rows) | ||
| 730 | { | ||
| 731 | featureOps[row.FieldAsString(0)] = row.Operation; | ||
| 732 | } | ||
| 733 | } | ||
| 734 | |||
| 735 | // Index Component table and check for keypath modifications | ||
| 736 | var componentKeyPath = new Dictionary<string, string>(); | ||
| 737 | var deletedComponent = new Dictionary<string, Row>(); | ||
| 738 | foreach (var row in componentTable.Rows) | ||
| 739 | { | ||
| 740 | var id = row.FieldAsString(0); | ||
| 741 | var keypath = row.FieldAsString(5) ?? String.Empty; | ||
| 742 | |||
| 743 | componentKeyPath.Add(id, keypath); | ||
| 744 | |||
| 745 | if (RowOperation.Delete == row.Operation) | ||
| 746 | { | ||
| 747 | deletedComponent.Add(id, row); | ||
| 748 | } | ||
| 749 | else if (RowOperation.Modify == row.Operation) | ||
| 750 | { | ||
| 751 | if (row.Fields[1].Modified) | ||
| 752 | { | ||
| 753 | // Changing the guid of a component is equal to deleting the old one and adding a new one. | ||
| 754 | deletedComponent.Add(id, row); | ||
| 755 | } | ||
| 756 | |||
| 757 | // If the keypath is modified its an error | ||
| 758 | if (row.Fields[5].Modified) | ||
| 759 | { | ||
| 760 | this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, id, transformPath)); | ||
| 761 | } | ||
| 762 | } | ||
| 763 | } | ||
| 764 | |||
| 765 | // Verify changes in the file table | ||
| 766 | if (transform.TryGetTable("File", out var fileTable)) | ||
| 767 | { | ||
| 768 | var componentWithChangedKeyPath = new Dictionary<string, string>(); | ||
| 769 | foreach (FileRow row in fileTable.Rows) | ||
| 770 | { | ||
| 771 | if (RowOperation.None == row.Operation) | ||
| 772 | { | ||
| 773 | continue; | ||
| 774 | } | ||
| 775 | |||
| 776 | var fileId = row.File; | ||
| 777 | var componentId = row.Component; | ||
| 778 | |||
| 779 | // If this file is the keypath of a component | ||
| 780 | if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal)) | ||
| 781 | { | ||
| 782 | if (row.Fields[2].Modified) | ||
| 783 | { | ||
| 784 | // You can't change the filename of a file that is the keypath of a component. | ||
| 785 | this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, componentId, transformPath)); | ||
| 786 | } | ||
| 787 | |||
| 788 | if (!componentWithChangedKeyPath.ContainsKey(componentId)) | ||
| 789 | { | ||
| 790 | componentWithChangedKeyPath.Add(componentId, fileId); | ||
| 791 | } | ||
| 792 | } | ||
| 793 | |||
| 794 | if (RowOperation.Delete == row.Operation) | ||
| 795 | { | ||
| 796 | // If the file is removed from a component that is not deleted. | ||
| 797 | if (!deletedComponent.ContainsKey(componentId)) | ||
| 798 | { | ||
| 799 | var foundRemoveFileEntry = false; | ||
| 800 | var filename = this.BackendHelper.GetMsiFileName(row.FieldAsString(2), false, true); | ||
| 801 | |||
| 802 | if (transform.TryGetTable("RemoveFile", out var removeFileTable)) | ||
| 803 | { | ||
| 804 | foreach (var removeFileRow in removeFileTable.Rows) | ||
| 805 | { | ||
| 806 | if (RowOperation.Delete == removeFileRow.Operation) | ||
| 807 | { | ||
| 808 | continue; | ||
| 809 | } | ||
| 810 | |||
| 811 | if (componentId == removeFileRow.FieldAsString(1)) | ||
| 812 | { | ||
| 813 | // Check if there is a RemoveFile entry for this file | ||
| 814 | if (null != removeFileRow[2]) | ||
| 815 | { | ||
| 816 | var removeFileName = this.BackendHelper.GetMsiFileName(removeFileRow.FieldAsString(2), false, true); | ||
| 817 | |||
| 818 | // Convert the MSI format for a wildcard string to Regex format. | ||
| 819 | removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); | ||
| 820 | |||
| 821 | var regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); | ||
| 822 | if (regex.IsMatch(filename)) | ||
| 823 | { | ||
| 824 | foundRemoveFileEntry = true; | ||
| 825 | break; | ||
| 826 | } | ||
| 827 | } | ||
| 828 | } | ||
| 829 | } | ||
| 830 | } | ||
| 831 | |||
| 832 | if (!foundRemoveFileEntry) | ||
| 833 | { | ||
| 834 | this.Messaging.Write(WarningMessages.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); | ||
| 835 | } | ||
| 836 | } | ||
| 837 | } | ||
| 838 | } | ||
| 839 | } | ||
| 840 | |||
| 841 | var featureComponentsTable = transform.Tables["FeatureComponents"]; | ||
| 842 | |||
| 843 | if (0 < deletedComponent.Count) | ||
| 844 | { | ||
| 845 | // Index FeatureComponents table. | ||
| 846 | var featureComponents = new Dictionary<string, List<string>>(); | ||
| 847 | |||
| 848 | if (null != featureComponentsTable) | ||
| 849 | { | ||
| 850 | foreach (var row in featureComponentsTable.Rows) | ||
| 851 | { | ||
| 852 | var componentId = row.FieldAsString(1); | ||
| 853 | |||
| 854 | if (!featureComponents.TryGetValue(componentId, out var features)) | ||
| 855 | { | ||
| 856 | features = new List<string>(); | ||
| 857 | featureComponents.Add(componentId, features); | ||
| 858 | } | ||
| 859 | |||
| 860 | features.Add(row.FieldAsString(0)); | ||
| 861 | } | ||
| 862 | } | ||
| 863 | |||
| 864 | // Check to make sure if a component was deleted, the feature was too. | ||
| 865 | foreach (var entry in deletedComponent) | ||
| 866 | { | ||
| 867 | if (featureComponents.TryGetValue(entry.Key, out var features)) | ||
| 868 | { | ||
| 869 | foreach (var featureId in features) | ||
| 870 | { | ||
| 871 | if (!featureOps.TryGetValue(featureId, out var op) || op != RowOperation.Delete) | ||
| 872 | { | ||
| 873 | // The feature was not deleted. | ||
| 874 | this.Messaging.Write(ErrorMessages.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, transformPath)); | ||
| 875 | } | ||
| 876 | } | ||
| 877 | } | ||
| 878 | } | ||
| 879 | } | ||
| 880 | |||
| 881 | // Warn if new components are added to existing features | ||
| 882 | if (null != featureComponentsTable) | ||
| 883 | { | ||
| 884 | foreach (var row in featureComponentsTable.Rows) | ||
| 885 | { | ||
| 886 | if (RowOperation.Add == row.Operation) | ||
| 887 | { | ||
| 888 | // Check if the feature is in the Feature table | ||
| 889 | var feature_ = row.FieldAsString(0); | ||
| 890 | var component_ = row.FieldAsString(1); | ||
| 891 | |||
| 892 | // Features may not be present if not referenced | ||
| 893 | if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) | ||
| 894 | { | ||
| 895 | this.Messaging.Write(WarningMessages.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, transformPath)); | ||
| 896 | } | ||
| 897 | } | ||
| 898 | } | ||
| 899 | } | ||
| 900 | } | ||
| 901 | |||
| 902 | /// <summary> | ||
| 903 | /// Remove the ProductCode property from the transform. | ||
| 904 | /// </summary> | ||
| 905 | /// <param name="transform">The transform.</param> | ||
| 906 | /// <remarks> | ||
| 907 | /// Changing the ProductCode is not supported in a patch. | ||
| 908 | /// </remarks> | ||
| 909 | private static void RemoveProductCodeFromTransform(WindowsInstallerData transform) | ||
| 910 | { | ||
| 911 | if (transform.Tables.TryGetTable("Property", out var propertyTable)) | ||
| 912 | { | ||
| 913 | for (var i = 0; i < propertyTable.Rows.Count; ++i) | ||
| 914 | { | ||
| 915 | var propertyRow = propertyTable.Rows[i]; | ||
| 916 | var property = (string)propertyRow[0]; | ||
| 917 | |||
| 918 | if ("ProductCode" == property) | ||
| 919 | { | ||
| 920 | propertyTable.Rows.RemoveAt(i); | ||
| 921 | break; | ||
| 922 | } | ||
| 923 | } | ||
| 924 | } | ||
| 925 | } | ||
| 926 | |||
| 927 | /// <summary> | ||
| 928 | /// Check if the section is in a PatchFamily. | ||
| 929 | /// </summary> | ||
| 930 | /// <param name="oldSection">Section id in target wixout</param> | ||
| 931 | /// <param name="newSection">Section id in upgrade wixout</param> | ||
| 932 | /// <param name="oldSections">Dictionary contains section id should be kept in the baseline wixout.</param> | ||
| 933 | /// <param name="newSections">Dictionary contains section id should be kept in the upgrade wixout.</param> | ||
| 934 | /// <returns>true if section in patch family</returns> | ||
| 935 | private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections) | ||
| 936 | { | ||
| 937 | var result = false; | ||
| 938 | |||
| 939 | if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection))) | ||
| 940 | { | ||
| 941 | result = true; | ||
| 942 | } | ||
| 943 | else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(newSection))) | ||
| 944 | { | ||
| 945 | result = true; | ||
| 946 | } | ||
| 947 | |||
| 948 | return result; | ||
| 949 | } | ||
| 950 | |||
| 951 | /// <summary> | ||
| 952 | /// Reduce the transform sequence tables. | ||
| 953 | /// </summary> | ||
| 954 | /// <param name="sequenceList">ArrayList of tables to be reduced</param> | ||
| 955 | /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param> | ||
| 956 | /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param> | ||
| 957 | /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param> | ||
| 958 | /// <returns>Number of rows left</returns> | ||
| 959 | private static int ReduceTransformSequenceTable(List<Table> sequenceList, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections, Dictionary<string, Row> customAction) | ||
| 960 | { | ||
| 961 | var keptRows = 0; | ||
| 962 | |||
| 963 | foreach (var currentTable in sequenceList) | ||
| 964 | { | ||
| 965 | for (var i = 0; i < currentTable.Rows.Count; i++) | ||
| 966 | { | ||
| 967 | var row = currentTable.Rows[i]; | ||
| 968 | var actionName = row.Fields[0].Data.ToString(); | ||
| 969 | var sections = row.SectionId.Split('/'); | ||
| 970 | var isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0); | ||
| 971 | |||
| 972 | if (row.Operation == RowOperation.None) | ||
| 973 | { | ||
| 974 | // Ignore the rows without section id. | ||
| 975 | if (isSectionIdEmpty) | ||
| 976 | { | ||
| 977 | currentTable.Rows.RemoveAt(i); | ||
| 978 | i--; | ||
| 979 | } | ||
| 980 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
| 981 | { | ||
| 982 | keptRows++; | ||
| 983 | } | ||
| 984 | else | ||
| 985 | { | ||
| 986 | currentTable.Rows.RemoveAt(i); | ||
| 987 | i--; | ||
| 988 | } | ||
| 989 | } | ||
| 990 | else if (row.Operation == RowOperation.Modify) | ||
| 991 | { | ||
| 992 | var sequenceChanged = row.Fields[2].Modified; | ||
| 993 | var conditionChanged = row.Fields[1].Modified; | ||
| 994 | |||
| 995 | if (sequenceChanged && !conditionChanged) | ||
| 996 | { | ||
| 997 | keptRows++; | ||
| 998 | } | ||
| 999 | else if (!sequenceChanged && conditionChanged) | ||
| 1000 | { | ||
| 1001 | if (isSectionIdEmpty) | ||
| 1002 | { | ||
| 1003 | currentTable.Rows.RemoveAt(i); | ||
| 1004 | i--; | ||
| 1005 | } | ||
| 1006 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
| 1007 | { | ||
| 1008 | keptRows++; | ||
| 1009 | } | ||
| 1010 | else | ||
| 1011 | { | ||
| 1012 | currentTable.Rows.RemoveAt(i); | ||
| 1013 | i--; | ||
| 1014 | } | ||
| 1015 | } | ||
| 1016 | else if (sequenceChanged && conditionChanged) | ||
| 1017 | { | ||
| 1018 | if (isSectionIdEmpty) | ||
| 1019 | { | ||
| 1020 | row.Fields[1].Modified = false; | ||
| 1021 | keptRows++; | ||
| 1022 | } | ||
| 1023 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
| 1024 | { | ||
| 1025 | keptRows++; | ||
| 1026 | } | ||
| 1027 | else | ||
| 1028 | { | ||
| 1029 | row.Fields[1].Modified = false; | ||
| 1030 | keptRows++; | ||
| 1031 | } | ||
| 1032 | } | ||
| 1033 | } | ||
| 1034 | else if (row.Operation == RowOperation.Delete) | ||
| 1035 | { | ||
| 1036 | if (isSectionIdEmpty) | ||
| 1037 | { | ||
| 1038 | // it is a stardard action which is added by wix, we should keep this action. | ||
| 1039 | row.Operation = RowOperation.None; | ||
| 1040 | keptRows++; | ||
| 1041 | } | ||
| 1042 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
| 1043 | { | ||
| 1044 | keptRows++; | ||
| 1045 | } | ||
| 1046 | else | ||
| 1047 | { | ||
| 1048 | if (customAction.ContainsKey(actionName)) | ||
| 1049 | { | ||
| 1050 | currentTable.Rows.RemoveAt(i); | ||
| 1051 | i--; | ||
| 1052 | } | ||
| 1053 | else | ||
| 1054 | { | ||
| 1055 | // it is a stardard action, we should keep this action. | ||
| 1056 | row.Operation = RowOperation.None; | ||
| 1057 | keptRows++; | ||
| 1058 | } | ||
| 1059 | } | ||
| 1060 | } | ||
| 1061 | else if (row.Operation == RowOperation.Add) | ||
| 1062 | { | ||
| 1063 | if (isSectionIdEmpty) | ||
| 1064 | { | ||
| 1065 | keptRows++; | ||
| 1066 | } | ||
| 1067 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
| 1068 | { | ||
| 1069 | keptRows++; | ||
| 1070 | } | ||
| 1071 | else | ||
| 1072 | { | ||
| 1073 | if (customAction.ContainsKey(actionName)) | ||
| 1074 | { | ||
| 1075 | currentTable.Rows.RemoveAt(i); | ||
| 1076 | i--; | ||
| 1077 | } | ||
| 1078 | else | ||
| 1079 | { | ||
| 1080 | keptRows++; | ||
| 1081 | } | ||
| 1082 | } | ||
| 1083 | } | ||
| 1084 | } | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | return keptRows; | ||
| 1088 | } | ||
| 1089 | |||
| 1090 | /// <summary> | ||
| 1091 | /// Create the #transform for the given main transform. | ||
| 1092 | /// </summary> | ||
| 1093 | private WindowsInstallerData BuildPairedTransform(Dictionary<SummaryInformationType, SummaryInformationSymbol> summaryInfo, Dictionary<string, MsiPatchMetadataSymbol> patchMetadata, WixPatchSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode) | ||
| 1094 | { | ||
| 1095 | productCode = null; | ||
| 1096 | |||
| 1097 | var pairedTransform = new WindowsInstallerData(null) | ||
| 1098 | { | ||
| 1099 | Type = OutputType.Transform, | ||
| 1100 | Codepage = mainTransform.Codepage | ||
| 1101 | }; | ||
| 1102 | |||
| 1103 | // lookup productVersion property to correct summaryInformation | ||
| 1104 | var newProductVersion = mainTransform.Tables["Property"]?.Rows.FirstOrDefault(r => r.FieldAsString(0) == "ProductVersion")?.FieldAsString(1); | ||
| 1105 | |||
| 1106 | var mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; | ||
| 1107 | var mainSummaryRows = mainSummaryTable.Rows.ToDictionary(r => r.FieldAsInteger(0)); | ||
| 1108 | |||
| 1109 | var baselineValidationFlags = ((int)baselineSymbol.ValidationFlags).ToString(CultureInfo.InvariantCulture); | ||
| 1110 | |||
| 1111 | if (!mainSummaryRows.ContainsKey((int)SummaryInformationType.TransformValidationFlags)) | ||
| 1112 | { | ||
| 1113 | var mainSummaryRow = mainSummaryTable.CreateRow(baselineSymbol.SourceLineNumbers); | ||
| 1114 | mainSummaryRow[0] = (int)SummaryInformationType.TransformValidationFlags; | ||
| 1115 | mainSummaryRow[1] = baselineValidationFlags; | ||
| 1116 | } | ||
| 1117 | |||
| 1118 | // copy summary information from core transform | ||
| 1119 | var pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); | ||
| 1120 | |||
| 1121 | foreach (var mainSummaryRow in mainSummaryTable.Rows) | ||
| 1122 | { | ||
| 1123 | var type = (SummaryInformationType)mainSummaryRow.FieldAsInteger(0); | ||
| 1124 | var value = mainSummaryRow.FieldAsString(1); | ||
| 1125 | switch (type) | ||
| 1126 | { | ||
| 1127 | case SummaryInformationType.TransformProductCodes: | ||
| 1128 | var propertyData = value.Split(';'); | ||
| 1129 | var oldProductVersion = propertyData[0].Substring(38); | ||
| 1130 | var upgradeCode = propertyData[2]; | ||
| 1131 | productCode = propertyData[0].Substring(0, 38); | ||
| 1132 | |||
| 1133 | if (newProductVersion == null) | ||
| 1134 | { | ||
| 1135 | newProductVersion = oldProductVersion; | ||
| 1136 | } | ||
| 1137 | |||
| 1138 | // Force mainTranform to 'old;new;upgrade' and pairedTransform to 'new;new;upgrade' | ||
| 1139 | mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); | ||
| 1140 | value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); | ||
| 1141 | break; | ||
| 1142 | case SummaryInformationType.TransformValidationFlags: // use validation flags authored into the patch XML. | ||
| 1143 | value = baselineValidationFlags; | ||
| 1144 | mainSummaryRow[1] = value; | ||
| 1145 | break; | ||
| 1146 | } | ||
| 1147 | |||
| 1148 | var pairedSummaryRow = pairedSummaryTable.CreateRow(mainSummaryRow.SourceLineNumbers); | ||
| 1149 | pairedSummaryRow[0] = mainSummaryRow[0]; | ||
| 1150 | pairedSummaryRow[1] = value; | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | if (productCode == null) | ||
| 1154 | { | ||
| 1155 | this.Messaging.Write(ErrorMessages.CouldNotDetermineProductCodeFromTransformSummaryInfo()); | ||
| 1156 | return null; | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | // Copy File table | ||
| 1160 | if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count) | ||
| 1161 | { | ||
| 1162 | var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); | ||
| 1163 | |||
| 1164 | foreach (FileRow mainFileRow in mainFileTable.Rows) | ||
| 1165 | { | ||
| 1166 | // Set File.Sequence to non null to satisfy transform bind. | ||
| 1167 | mainFileRow.Sequence = 1; | ||
| 1168 | |||
| 1169 | // Delete's don't need rows in the paired transform. | ||
| 1170 | if (mainFileRow.Operation == RowOperation.Delete) | ||
| 1171 | { | ||
| 1172 | continue; | ||
| 1173 | } | ||
| 1174 | |||
| 1175 | var pairedFileRow = (FileRow)pairedFileTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 1176 | pairedFileRow.Operation = RowOperation.Modify; | ||
| 1177 | mainFileRow.CopyTo(pairedFileRow); | ||
| 1178 | |||
| 1179 | // Override authored media for patch bind. | ||
| 1180 | mainFileRow.DiskId = mediaSymbol.DiskId; | ||
| 1181 | |||
| 1182 | // Suppress any change to File.Sequence to avoid bloat. | ||
| 1183 | mainFileRow.Fields[7].Modified = false; | ||
| 1184 | |||
| 1185 | // Force File row to appear in the transform. | ||
| 1186 | switch (mainFileRow.Operation) | ||
| 1187 | { | ||
| 1188 | case RowOperation.Modify: | ||
| 1189 | case RowOperation.Add: | ||
| 1190 | pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; | ||
| 1191 | pairedFileRow.Fields[6].Modified = true; | ||
| 1192 | pairedFileRow.Operation = mainFileRow.Operation; | ||
| 1193 | break; | ||
| 1194 | default: | ||
| 1195 | pairedFileRow.Fields[6].Modified = false; | ||
| 1196 | break; | ||
| 1197 | } | ||
| 1198 | } | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | // Add Media row to pairedTransform | ||
| 1202 | var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); | ||
| 1203 | var pairedMediaRow = (MediaRow)pairedMediaTable.CreateRow(mediaSymbol.SourceLineNumbers); | ||
| 1204 | pairedMediaRow.Operation = RowOperation.Add; | ||
| 1205 | pairedMediaRow.DiskId = mediaSymbol.DiskId; | ||
| 1206 | pairedMediaRow.LastSequence = mediaSymbol.LastSequence ?? 0; | ||
| 1207 | pairedMediaRow.DiskPrompt = mediaSymbol.DiskPrompt; | ||
| 1208 | pairedMediaRow.Cabinet = mediaSymbol.Cabinet; | ||
| 1209 | pairedMediaRow.VolumeLabel = mediaSymbol.VolumeLabel; | ||
| 1210 | pairedMediaRow.Source = mediaSymbol.Source; | ||
| 1211 | |||
| 1212 | // Add PatchPackage for this Media | ||
| 1213 | var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); | ||
| 1214 | pairedPackageTable.Operation = TableOperation.Add; | ||
| 1215 | var pairedPackageRow = pairedPackageTable.CreateRow(mediaSymbol.SourceLineNumbers); | ||
| 1216 | pairedPackageRow.Operation = RowOperation.Add; | ||
| 1217 | pairedPackageRow[0] = patchIdSymbol.Id.Id; | ||
| 1218 | pairedPackageRow[1] = mediaSymbol.DiskId; | ||
| 1219 | |||
| 1220 | // Add the property to the patch transform's Property table. | ||
| 1221 | var pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); | ||
| 1222 | pairedPropertyTable.Operation = TableOperation.Add; | ||
| 1223 | |||
| 1224 | // Add property to both identify client patches and whether those patches are removable or not | ||
| 1225 | patchMetadata.TryGetValue("AllowRemoval", out var allowRemovalSymbol); | ||
| 1226 | |||
| 1227 | var pairedPropertyRow = pairedPropertyTable.CreateRow(allowRemovalSymbol?.SourceLineNumbers); | ||
| 1228 | pairedPropertyRow.Operation = RowOperation.Add; | ||
| 1229 | pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".AllowRemoval"); | ||
| 1230 | pairedPropertyRow[1] = allowRemovalSymbol?.Value ?? "0"; | ||
| 1231 | |||
| 1232 | // Add this patch code GUID to the patch transform to identify | ||
| 1233 | // which patches are installed, including in multi-patch | ||
| 1234 | // installations. | ||
| 1235 | pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); | ||
| 1236 | pairedPropertyRow.Operation = RowOperation.Add; | ||
| 1237 | pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".PatchCode"); | ||
| 1238 | pairedPropertyRow[1] = patchIdSymbol.Id.Id; | ||
| 1239 | |||
| 1240 | // Add PATCHNEWPACKAGECODE to apply to admin layouts. | ||
| 1241 | pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); | ||
| 1242 | pairedPropertyRow.Operation = RowOperation.Add; | ||
| 1243 | pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; | ||
| 1244 | pairedPropertyRow[1] = patchIdSymbol.Id.Id; | ||
| 1245 | |||
| 1246 | // Add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts. | ||
| 1247 | if (summaryInfo.TryGetValue(SummaryInformationType.Subject, out var subjectSymbol)) | ||
| 1248 | { | ||
| 1249 | pairedPropertyRow = pairedPropertyTable.CreateRow(subjectSymbol.SourceLineNumbers); | ||
| 1250 | pairedPropertyRow.Operation = RowOperation.Add; | ||
| 1251 | pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; | ||
| 1252 | pairedPropertyRow[1] = subjectSymbol.Value; | ||
| 1253 | } | ||
| 1254 | |||
| 1255 | if (summaryInfo.TryGetValue(SummaryInformationType.Comments, out var commentsSymbol)) | ||
| 1256 | { | ||
| 1257 | pairedPropertyRow = pairedPropertyTable.CreateRow(commentsSymbol.SourceLineNumbers); | ||
| 1258 | pairedPropertyRow.Operation = RowOperation.Add; | ||
| 1259 | pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; | ||
| 1260 | pairedPropertyRow[1] = commentsSymbol.Value; | ||
| 1261 | } | ||
| 1262 | |||
| 1263 | return pairedTransform; | ||
| 1264 | } | ||
| 1265 | |||
| 1266 | private static SortedSet<string> FinalizePatchProductCodes(List<IntermediateSymbol> symbols, SortedSet<string> productCodes) | ||
| 1267 | { | ||
| 1268 | var patchTargetSymbols = symbols.OfType<WixPatchTargetSymbol>().ToList(); | ||
| 1269 | |||
| 1270 | if (patchTargetSymbols.Any()) | ||
| 1271 | { | ||
| 1272 | var targets = new SortedSet<string>(); | ||
| 1273 | var replace = true; | ||
| 1274 | foreach (var wixPatchTargetRow in patchTargetSymbols) | ||
| 1275 | { | ||
| 1276 | var target = wixPatchTargetRow.ProductCode.ToUpperInvariant(); | ||
| 1277 | if (target == "*") | ||
| 1278 | { | ||
| 1279 | replace = false; | ||
| 1280 | } | ||
| 1281 | else | ||
| 1282 | { | ||
| 1283 | targets.Add(target); | ||
| 1284 | } | ||
| 1285 | } | ||
| 1286 | |||
| 1287 | // Replace the target ProductCodes with the authored list. | ||
| 1288 | if (replace) | ||
| 1289 | { | ||
| 1290 | productCodes = targets; | ||
| 1291 | } | ||
| 1292 | else | ||
| 1293 | { | ||
| 1294 | // Copy the authored target ProductCodes into the list. | ||
| 1295 | foreach (var target in targets) | ||
| 1296 | { | ||
| 1297 | productCodes.Add(target); | ||
| 1298 | } | ||
| 1299 | } | ||
| 1300 | } | ||
| 1301 | |||
| 1302 | return productCodes; | ||
| 1303 | } | ||
| 1304 | } | ||
| 1305 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs new file mode 100644 index 00000000..9f36cd78 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs | |||
| @@ -0,0 +1,646 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Data.WindowsInstaller; | ||
| 12 | using WixToolset.Extensibility; | ||
| 13 | using WixToolset.Extensibility.Data; | ||
| 14 | using WixToolset.Extensibility.Services; | ||
| 15 | |||
| 16 | /// <summary> | ||
| 17 | /// Binds a databse. | ||
| 18 | /// </summary> | ||
| 19 | internal class BindDatabaseCommand | ||
| 20 | { | ||
| 21 | // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. | ||
| 22 | internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); | ||
| 23 | |||
| 24 | public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, string cubeFile) : this(context, backendExtension, null, cubeFile) | ||
| 25 | { | ||
| 26 | } | ||
| 27 | |||
| 28 | public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, IEnumerable<SubStorage> subStorages, string cubeFile) | ||
| 29 | { | ||
| 30 | this.ServiceProvider = context.ServiceProvider; | ||
| 31 | |||
| 32 | this.Messaging = context.ServiceProvider.GetService<IMessaging>(); | ||
| 33 | |||
| 34 | this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService<IWindowsInstallerBackendHelper>(); | ||
| 35 | |||
| 36 | this.PathResolver = this.ServiceProvider.GetService<IPathResolver>(); | ||
| 37 | |||
| 38 | this.CabbingThreadCount = context.CabbingThreadCount; | ||
| 39 | this.CabCachePath = context.CabCachePath; | ||
| 40 | this.DefaultCompressionLevel = context.DefaultCompressionLevel; | ||
| 41 | this.DelayedFields = context.DelayedFields; | ||
| 42 | this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; | ||
| 43 | this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions); | ||
| 44 | this.Intermediate = context.IntermediateRepresentation; | ||
| 45 | this.IntermediateFolder = context.IntermediateFolder; | ||
| 46 | this.OutputPath = context.OutputPath; | ||
| 47 | this.OutputPdbPath = context.PdbPath; | ||
| 48 | this.PdbType = context.PdbType; | ||
| 49 | this.ResolvedCodepage = context.ResolvedCodepage; | ||
| 50 | this.ResolvedSummaryInformationCodepage = context.ResolvedSummaryInformationCodepage; | ||
| 51 | this.ResolvedLcid = context.ResolvedLcid; | ||
| 52 | this.SuppressLayout = context.SuppressLayout; | ||
| 53 | |||
| 54 | this.SubStorages = subStorages; | ||
| 55 | |||
| 56 | this.SuppressValidation = context.SuppressValidation; | ||
| 57 | this.Ices = context.Ices; | ||
| 58 | this.SuppressedIces = context.SuppressIces; | ||
| 59 | this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile }; | ||
| 60 | |||
| 61 | this.BackendExtensions = backendExtension; | ||
| 62 | } | ||
| 63 | |||
| 64 | public IServiceProvider ServiceProvider { get; } | ||
| 65 | |||
| 66 | private IMessaging Messaging { get; } | ||
| 67 | |||
| 68 | private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } | ||
| 69 | |||
| 70 | private IPathResolver PathResolver { get; } | ||
| 71 | |||
| 72 | private int CabbingThreadCount { get; } | ||
| 73 | |||
| 74 | private string CabCachePath { get; } | ||
| 75 | |||
| 76 | private CompressionLevel? DefaultCompressionLevel { get; } | ||
| 77 | |||
| 78 | public IEnumerable<IDelayedField> DelayedFields { get; } | ||
| 79 | |||
| 80 | public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; } | ||
| 81 | |||
| 82 | public FileSystemManager FileSystemManager { get; } | ||
| 83 | |||
| 84 | public bool DeltaBinaryPatch { get; set; } | ||
| 85 | |||
| 86 | private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; } | ||
| 87 | |||
| 88 | private IEnumerable<SubStorage> SubStorages { get; } | ||
| 89 | |||
| 90 | private Intermediate Intermediate { get; } | ||
| 91 | |||
| 92 | private string OutputPath { get; } | ||
| 93 | |||
| 94 | public PdbType PdbType { get; set; } | ||
| 95 | |||
| 96 | private string OutputPdbPath { get; } | ||
| 97 | |||
| 98 | private int? ResolvedCodepage { get; } | ||
| 99 | |||
| 100 | private int? ResolvedSummaryInformationCodepage { get; } | ||
| 101 | |||
| 102 | private int? ResolvedLcid { get; } | ||
| 103 | |||
| 104 | private bool SuppressAddingValidationRows { get; } | ||
| 105 | |||
| 106 | private bool SuppressLayout { get; } | ||
| 107 | |||
| 108 | private string IntermediateFolder { get; } | ||
| 109 | |||
| 110 | private bool SuppressValidation { get; } | ||
| 111 | |||
| 112 | private IEnumerable<string> Ices { get; } | ||
| 113 | |||
| 114 | private IEnumerable<string> SuppressedIces { get; } | ||
| 115 | |||
| 116 | private IEnumerable<string> CubeFiles { get; } | ||
| 117 | |||
| 118 | public IBindResult Execute() | ||
| 119 | { | ||
| 120 | if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) | ||
| 121 | { | ||
| 122 | this.Messaging.Write(ErrorMessages.IntermediatesMustBeResolved(this.Intermediate.Id)); | ||
| 123 | } | ||
| 124 | |||
| 125 | var section = this.Intermediate.Sections.Single(); | ||
| 126 | |||
| 127 | var packageSymbol = (section.Type == SectionType.Product) ? this.GetSingleSymbol<WixPackageSymbol>(section) : null; | ||
| 128 | var moduleSymbol = (section.Type == SectionType.Module) ? this.GetSingleSymbol<WixModuleSymbol>(section) : null; | ||
| 129 | var patchSymbol = (section.Type == SectionType.Patch) ? this.GetSingleSymbol<WixPatchSymbol>(section) : null; | ||
| 130 | |||
| 131 | var fileTransfers = new List<IFileTransfer>(); | ||
| 132 | var trackedFiles = new List<ITrackedFile>(); | ||
| 133 | |||
| 134 | var containsMergeModules = false; | ||
| 135 | |||
| 136 | // Load standard tables, authored custom tables, and extension custom tables. | ||
| 137 | TableDefinitionCollection tableDefinitions; | ||
| 138 | { | ||
| 139 | var command = new LoadTableDefinitionsCommand(this.Messaging, section, this.BackendExtensions); | ||
| 140 | command.Execute(); | ||
| 141 | |||
| 142 | tableDefinitions = command.TableDefinitions; | ||
| 143 | } | ||
| 144 | |||
| 145 | // Calculate codepage | ||
| 146 | var codepage = this.CalculateCodepage(packageSymbol, moduleSymbol, patchSymbol); | ||
| 147 | |||
| 148 | // Process properties and create the delayed variable cache if needed. | ||
| 149 | Dictionary<string, string> variableCache = null; | ||
| 150 | string productLanguage = null; | ||
| 151 | { | ||
| 152 | var command = new ProcessPropertiesCommand(section, packageSymbol, this.ResolvedLcid ?? 0, this.DelayedFields.Any(), this.WindowsInstallerBackendHelper); | ||
| 153 | command.Execute(); | ||
| 154 | |||
| 155 | variableCache = command.DelayedVariablesCache; | ||
| 156 | productLanguage = command.ProductLanguage; | ||
| 157 | } | ||
| 158 | |||
| 159 | // Process the summary information table after properties are processed. | ||
| 160 | bool compressed; | ||
| 161 | bool longNames; | ||
| 162 | int installerVersion; | ||
| 163 | Platform platform; | ||
| 164 | string modularizationSuffix; | ||
| 165 | { | ||
| 166 | var branding = this.ServiceProvider.GetService<IWixBranding>(); | ||
| 167 | |||
| 168 | var command = new BindSummaryInfoCommand(section, this.ResolvedSummaryInformationCodepage, productLanguage, this.WindowsInstallerBackendHelper, branding); | ||
| 169 | command.Execute(); | ||
| 170 | |||
| 171 | compressed = command.Compressed; | ||
| 172 | longNames = command.LongNames; | ||
| 173 | installerVersion = command.InstallerVersion; | ||
| 174 | platform = command.Platform; | ||
| 175 | modularizationSuffix = command.ModularizationSuffix; | ||
| 176 | } | ||
| 177 | |||
| 178 | // Sequence all the actions. | ||
| 179 | { | ||
| 180 | var command = new SequenceActionsCommand(this.Messaging, section); | ||
| 181 | command.Execute(); | ||
| 182 | } | ||
| 183 | |||
| 184 | if (section.Type == SectionType.Product || section.Type == SectionType.Module) | ||
| 185 | { | ||
| 186 | var command = new AddRequiredStandardDirectories(section, platform); | ||
| 187 | command.Execute(); | ||
| 188 | } | ||
| 189 | |||
| 190 | { | ||
| 191 | var command = new CreateSpecialPropertiesCommand(section); | ||
| 192 | command.Execute(); | ||
| 193 | } | ||
| 194 | |||
| 195 | #if TODO_PATCHING | ||
| 196 | ////if (OutputType.Patch == this.Output.Type) | ||
| 197 | ////{ | ||
| 198 | //// foreach (SubStorage substorage in this.Output.SubStorages) | ||
| 199 | //// { | ||
| 200 | //// Output transform = substorage.Data; | ||
| 201 | |||
| 202 | //// ResolveFieldsCommand command = new ResolveFieldsCommand(); | ||
| 203 | //// command.Tables = transform.Tables; | ||
| 204 | //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 205 | //// command.FileManagerCore = this.FileManagerCore; | ||
| 206 | //// command.FileManagers = this.FileManagers; | ||
| 207 | //// command.SupportDelayedResolution = false; | ||
| 208 | //// command.TempFilesLocation = this.TempFilesLocation; | ||
| 209 | //// command.WixVariableResolver = this.WixVariableResolver; | ||
| 210 | //// command.Execute(); | ||
| 211 | |||
| 212 | //// this.MergeUnrealTables(transform.Tables); | ||
| 213 | //// } | ||
| 214 | ////} | ||
| 215 | #endif | ||
| 216 | |||
| 217 | if (this.Messaging.EncounteredError) | ||
| 218 | { | ||
| 219 | return null; | ||
| 220 | } | ||
| 221 | |||
| 222 | this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); | ||
| 223 | this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); | ||
| 224 | |||
| 225 | // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). | ||
| 226 | { | ||
| 227 | var extractedFiles = this.WindowsInstallerBackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); | ||
| 228 | |||
| 229 | trackedFiles.AddRange(extractedFiles); | ||
| 230 | } | ||
| 231 | |||
| 232 | // This must occur after all variables and source paths have been resolved. | ||
| 233 | List<IFileFacade> fileFacades; | ||
| 234 | if (SectionType.Patch == section.Type) | ||
| 235 | { | ||
| 236 | var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.SubStorages); | ||
| 237 | command.Execute(); | ||
| 238 | |||
| 239 | fileFacades = command.FileFacades; | ||
| 240 | } | ||
| 241 | else | ||
| 242 | { | ||
| 243 | var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper); | ||
| 244 | command.Execute(); | ||
| 245 | |||
| 246 | fileFacades = command.FileFacades; | ||
| 247 | } | ||
| 248 | |||
| 249 | // Retrieve file information from merge modules. | ||
| 250 | if (SectionType.Product == section.Type) | ||
| 251 | { | ||
| 252 | var wixMergeSymbols = section.Symbols.OfType<WixMergeSymbol>().ToList(); | ||
| 253 | |||
| 254 | if (wixMergeSymbols.Any()) | ||
| 255 | { | ||
| 256 | containsMergeModules = true; | ||
| 257 | |||
| 258 | var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacades, installerVersion, this.IntermediateFolder, this.SuppressLayout); | ||
| 259 | command.Execute(); | ||
| 260 | |||
| 261 | fileFacades.AddRange(command.MergeModulesFileFacades); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | // stop processing if an error previously occurred | ||
| 266 | if (this.Messaging.EncounteredError) | ||
| 267 | { | ||
| 268 | return null; | ||
| 269 | } | ||
| 270 | |||
| 271 | // Process SoftwareTags in MSI packages. | ||
| 272 | if (SectionType.Product == section.Type) | ||
| 273 | { | ||
| 274 | var softwareTags = section.Symbols.OfType<WixProductTagSymbol>().ToList(); | ||
| 275 | |||
| 276 | if (softwareTags.Any()) | ||
| 277 | { | ||
| 278 | var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder); | ||
| 279 | command.Execute(); | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | // Gather information about files that do not come from merge modules. | ||
| 284 | { | ||
| 285 | var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); | ||
| 286 | command.Execute(); | ||
| 287 | } | ||
| 288 | |||
| 289 | // stop processing if an error previously occurred | ||
| 290 | if (this.Messaging.EncounteredError) | ||
| 291 | { | ||
| 292 | return null; | ||
| 293 | } | ||
| 294 | |||
| 295 | // Now that the variable cache is populated, resolve any delayed fields. | ||
| 296 | if (this.DelayedFields.Any()) | ||
| 297 | { | ||
| 298 | this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); | ||
| 299 | } | ||
| 300 | |||
| 301 | // Update symbols that reference text files on disk. | ||
| 302 | { | ||
| 303 | var command = new UpdateFromTextFilesCommand(this.Messaging, section); | ||
| 304 | command.Execute(); | ||
| 305 | } | ||
| 306 | |||
| 307 | // Add missing CreateFolder symbols to null-keypath components. | ||
| 308 | { | ||
| 309 | var command = new AddCreateFoldersCommand(section); | ||
| 310 | command.Execute(); | ||
| 311 | } | ||
| 312 | |||
| 313 | // Process dependency references. | ||
| 314 | if (SectionType.Product == section.Type || SectionType.Module == section.Type) | ||
| 315 | { | ||
| 316 | var dependencyRefs = section.Symbols.OfType<WixDependencyRefSymbol>().ToList(); | ||
| 317 | |||
| 318 | if (dependencyRefs.Any()) | ||
| 319 | { | ||
| 320 | var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs); | ||
| 321 | command.Execute(); | ||
| 322 | } | ||
| 323 | } | ||
| 324 | |||
| 325 | // If there are any backend extensions, give them the opportunity to process | ||
| 326 | // the section now that the fields have all be resolved. | ||
| 327 | // | ||
| 328 | if (this.BackendExtensions.Any()) | ||
| 329 | { | ||
| 330 | using (new IntermediateFieldContext("wix.bind.finalize")) | ||
| 331 | { | ||
| 332 | foreach (var extension in this.BackendExtensions) | ||
| 333 | { | ||
| 334 | extension.SymbolsFinalized(section); | ||
| 335 | } | ||
| 336 | |||
| 337 | var reresolvedFiles = section.Symbols | ||
| 338 | .OfType<FileSymbol>() | ||
| 339 | .Where(s => s.Fields.Any(f => f?.Context == "wix.bind.finalize")) | ||
| 340 | .ToList(); | ||
| 341 | |||
| 342 | if (reresolvedFiles.Any()) | ||
| 343 | { | ||
| 344 | var updatedFacades = reresolvedFiles.Select(f => fileFacades.First(ff => ff.Id == f.Id?.Id)); | ||
| 345 | |||
| 346 | var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updatedFacades, variableCache, overwriteHash: false); | ||
| 347 | command.Execute(); | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | if (this.Messaging.EncounteredError) | ||
| 352 | { | ||
| 353 | return null; | ||
| 354 | } | ||
| 355 | } | ||
| 356 | |||
| 357 | // Set generated component guids and validate all guids. | ||
| 358 | { | ||
| 359 | var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); | ||
| 360 | command.Execute(); | ||
| 361 | } | ||
| 362 | |||
| 363 | // Assign files to media and update file sequences. | ||
| 364 | Dictionary<MediaSymbol, IEnumerable<IFileFacade>> filesByCabinetMedia; | ||
| 365 | IEnumerable<IFileFacade> uncompressedFiles; | ||
| 366 | { | ||
| 367 | var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, fileFacades); | ||
| 368 | order.Execute(); | ||
| 369 | |||
| 370 | fileFacades = order.FileFacades; | ||
| 371 | |||
| 372 | var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); | ||
| 373 | assign.Execute(); | ||
| 374 | |||
| 375 | filesByCabinetMedia = assign.FileFacadesByCabinetMedia; | ||
| 376 | uncompressedFiles = assign.UncompressedFileFacades; | ||
| 377 | |||
| 378 | var update = new UpdateMediaSequencesCommand(section, fileFacades); | ||
| 379 | update.Execute(); | ||
| 380 | } | ||
| 381 | |||
| 382 | // stop processing if an error previously occurred | ||
| 383 | if (this.Messaging.EncounteredError) | ||
| 384 | { | ||
| 385 | return null; | ||
| 386 | } | ||
| 387 | |||
| 388 | // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better. | ||
| 389 | WindowsInstallerData data; | ||
| 390 | { | ||
| 391 | var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, codepage, this.BackendExtensions, this.WindowsInstallerBackendHelper); | ||
| 392 | data = command.Execute(); | ||
| 393 | } | ||
| 394 | |||
| 395 | IEnumerable<string> suppressedTableNames = null; | ||
| 396 | if (data.Type == OutputType.Module) | ||
| 397 | { | ||
| 398 | // Modularize identifiers. | ||
| 399 | var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType<WixSuppressModularizationSymbol>()); | ||
| 400 | modularize.Execute(); | ||
| 401 | |||
| 402 | // Ensure all sequence tables in place because, mergemod.dll requires them. | ||
| 403 | var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions); | ||
| 404 | suppressedTableNames = unsuppress.Execute(); | ||
| 405 | } | ||
| 406 | else if (data.Type == OutputType.Patch) | ||
| 407 | { | ||
| 408 | foreach (var storage in this.SubStorages) | ||
| 409 | { | ||
| 410 | data.SubStorages.Add(storage); | ||
| 411 | } | ||
| 412 | } | ||
| 413 | |||
| 414 | // Stop processing if an error previously occurred. | ||
| 415 | if (this.Messaging.EncounteredError) | ||
| 416 | { | ||
| 417 | return null; | ||
| 418 | } | ||
| 419 | |||
| 420 | // Ensure the intermediate folder is created since delta patches will be | ||
| 421 | // created there. | ||
| 422 | Directory.CreateDirectory(this.IntermediateFolder); | ||
| 423 | |||
| 424 | if (SectionType.Patch == section.Type && this.DeltaBinaryPatch) | ||
| 425 | { | ||
| 426 | var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType<WixPatchSymbol>().FirstOrDefault()); | ||
| 427 | command.Execute(); | ||
| 428 | } | ||
| 429 | |||
| 430 | // create cabinet files and process uncompressed files | ||
| 431 | var layoutDirectory = Path.GetDirectoryName(this.OutputPath); | ||
| 432 | if (!this.SuppressLayout || OutputType.Module == data.Type) | ||
| 433 | { | ||
| 434 | this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); | ||
| 435 | |||
| 436 | var mediaTemplate = section.Symbols.OfType<WixMediaTemplateSymbol>().FirstOrDefault(); | ||
| 437 | |||
| 438 | var command = new CreateCabinetsCommand(this.ServiceProvider, this.WindowsInstallerBackendHelper, mediaTemplate); | ||
| 439 | command.CabbingThreadCount = this.CabbingThreadCount; | ||
| 440 | command.CabCachePath = this.CabCachePath; | ||
| 441 | command.DefaultCompressionLevel = this.DefaultCompressionLevel; | ||
| 442 | command.Data = data; | ||
| 443 | command.Messaging = this.Messaging; | ||
| 444 | command.BackendExtensions = this.BackendExtensions; | ||
| 445 | command.LayoutDirectory = layoutDirectory; | ||
| 446 | command.Compressed = compressed; | ||
| 447 | command.ModularizationSuffix = modularizationSuffix; | ||
| 448 | command.FileFacadesByCabinet = filesByCabinetMedia; | ||
| 449 | command.ResolveMedia = this.ResolveMedia; | ||
| 450 | command.TableDefinitions = tableDefinitions; | ||
| 451 | command.IntermediateFolder = this.IntermediateFolder; | ||
| 452 | command.Execute(); | ||
| 453 | |||
| 454 | fileTransfers.AddRange(command.FileTransfers); | ||
| 455 | trackedFiles.AddRange(command.TrackedFiles); | ||
| 456 | } | ||
| 457 | |||
| 458 | // stop processing if an error previously occurred | ||
| 459 | if (this.Messaging.EncounteredError) | ||
| 460 | { | ||
| 461 | return null; | ||
| 462 | } | ||
| 463 | |||
| 464 | // We can create instance transforms since Component Guids and Outputs are created. | ||
| 465 | if (data.Type == OutputType.Product) | ||
| 466 | { | ||
| 467 | var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper); | ||
| 468 | command.Execute(); | ||
| 469 | } | ||
| 470 | else if (data.Type == OutputType.Patch) | ||
| 471 | { | ||
| 472 | // Copy output data back into the transforms. | ||
| 473 | var command = new UpdateTransformsWithFileFacades(this.Messaging, data, this.SubStorages, tableDefinitions, fileFacades); | ||
| 474 | command.Execute(); | ||
| 475 | } | ||
| 476 | |||
| 477 | // Generate database file. | ||
| 478 | { | ||
| 479 | this.Messaging.Write(VerboseMessages.GeneratingDatabase()); | ||
| 480 | |||
| 481 | var trackMsi = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); | ||
| 482 | trackedFiles.Add(trackMsi); | ||
| 483 | |||
| 484 | var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); | ||
| 485 | command.Execute(); | ||
| 486 | |||
| 487 | trackedFiles.AddRange(command.GeneratedTemporaryFiles); | ||
| 488 | } | ||
| 489 | |||
| 490 | // Stop processing if an error previously occurred. | ||
| 491 | if (this.Messaging.EncounteredError) | ||
| 492 | { | ||
| 493 | return null; | ||
| 494 | } | ||
| 495 | |||
| 496 | // Merge modules. | ||
| 497 | if (containsMergeModules) | ||
| 498 | { | ||
| 499 | this.Messaging.Write(VerboseMessages.MergingModules()); | ||
| 500 | |||
| 501 | var command = new MergeModulesCommand(this.Messaging, fileFacades, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); | ||
| 502 | command.Execute(); | ||
| 503 | } | ||
| 504 | |||
| 505 | if (this.Messaging.EncounteredError) | ||
| 506 | { | ||
| 507 | return null; | ||
| 508 | } | ||
| 509 | |||
| 510 | // Validate the output if there are CUBe files and we're not explicitly suppressing validation. | ||
| 511 | if (this.CubeFiles != null && !this.SuppressValidation) | ||
| 512 | { | ||
| 513 | var command = new ValidateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces); | ||
| 514 | command.Execute(); | ||
| 515 | |||
| 516 | trackedFiles.AddRange(command.TrackedFiles); | ||
| 517 | } | ||
| 518 | |||
| 519 | if (this.Messaging.EncounteredError) | ||
| 520 | { | ||
| 521 | return null; | ||
| 522 | } | ||
| 523 | |||
| 524 | // Process uncompressed files. | ||
| 525 | if (!this.SuppressLayout && uncompressedFiles.Any()) | ||
| 526 | { | ||
| 527 | var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver); | ||
| 528 | command.Compressed = compressed; | ||
| 529 | command.FileFacades = uncompressedFiles; | ||
| 530 | command.LayoutDirectory = layoutDirectory; | ||
| 531 | command.LongNamesInImage = longNames; | ||
| 532 | command.ResolveMedia = this.ResolveMedia; | ||
| 533 | command.DatabasePath = this.OutputPath; | ||
| 534 | command.Execute(); | ||
| 535 | |||
| 536 | fileTransfers.AddRange(command.FileTransfers); | ||
| 537 | trackedFiles.AddRange(command.TrackedFiles); | ||
| 538 | } | ||
| 539 | |||
| 540 | // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables). | ||
| 541 | trackedFiles.AddRange(fileFacades.Select(f => this.WindowsInstallerBackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber))); | ||
| 542 | |||
| 543 | var result = this.ServiceProvider.GetService<IBindResult>(); | ||
| 544 | result.FileTransfers = fileTransfers; | ||
| 545 | result.TrackedFiles = trackedFiles; | ||
| 546 | result.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, data); | ||
| 547 | |||
| 548 | return result; | ||
| 549 | } | ||
| 550 | |||
| 551 | private int CalculateCodepage(WixPackageSymbol packageSymbol, WixModuleSymbol moduleSymbol, WixPatchSymbol patchSymbol) | ||
| 552 | { | ||
| 553 | var codepage = packageSymbol?.Codepage ?? moduleSymbol?.Codepage ?? patchSymbol?.Codepage; | ||
| 554 | |||
| 555 | if (String.IsNullOrEmpty(codepage)) | ||
| 556 | { | ||
| 557 | codepage = this.ResolvedCodepage?.ToString() ?? "65001"; | ||
| 558 | |||
| 559 | if (packageSymbol != null) | ||
| 560 | { | ||
| 561 | packageSymbol.Codepage = codepage; | ||
| 562 | } | ||
| 563 | else if (moduleSymbol != null) | ||
| 564 | { | ||
| 565 | moduleSymbol.Codepage = codepage; | ||
| 566 | } | ||
| 567 | else if (patchSymbol != null) | ||
| 568 | { | ||
| 569 | patchSymbol.Codepage = codepage; | ||
| 570 | } | ||
| 571 | } | ||
| 572 | |||
| 573 | return this.WindowsInstallerBackendHelper.GetValidCodePage(codepage); | ||
| 574 | } | ||
| 575 | |||
| 576 | private T GetSingleSymbol<T>(IntermediateSection section) where T : IntermediateSymbol | ||
| 577 | { | ||
| 578 | var symbols = section.Symbols.OfType<T>().ToList(); | ||
| 579 | |||
| 580 | if (1 != symbols.Count) | ||
| 581 | { | ||
| 582 | throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); | ||
| 583 | } | ||
| 584 | |||
| 585 | return symbols[0]; | ||
| 586 | } | ||
| 587 | |||
| 588 | private WixOutput CreateWixout(List<ITrackedFile> trackedFiles, Intermediate intermediate, WindowsInstallerData data) | ||
| 589 | { | ||
| 590 | WixOutput wixout; | ||
| 591 | |||
| 592 | if (String.IsNullOrEmpty(this.OutputPdbPath)) | ||
| 593 | { | ||
| 594 | wixout = WixOutput.Create(); | ||
| 595 | } | ||
| 596 | else | ||
| 597 | { | ||
| 598 | var trackPdb = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); | ||
| 599 | trackedFiles.Add(trackPdb); | ||
| 600 | |||
| 601 | wixout = WixOutput.Create(trackPdb.Path); | ||
| 602 | } | ||
| 603 | |||
| 604 | intermediate.Save(wixout); | ||
| 605 | |||
| 606 | data.Save(wixout); | ||
| 607 | |||
| 608 | wixout.Reopen(); | ||
| 609 | |||
| 610 | return wixout; | ||
| 611 | } | ||
| 612 | |||
| 613 | private string ResolveMedia(MediaSymbol media, string mediaLayoutDirectory, string layoutDirectory) | ||
| 614 | { | ||
| 615 | string layout = null; | ||
| 616 | |||
| 617 | foreach (var extension in this.BackendExtensions) | ||
| 618 | { | ||
| 619 | layout = extension.ResolveMedia(media, mediaLayoutDirectory, layoutDirectory); | ||
| 620 | if (!String.IsNullOrEmpty(layout)) | ||
| 621 | { | ||
| 622 | break; | ||
| 623 | } | ||
| 624 | } | ||
| 625 | |||
| 626 | // If no binder file manager resolved the layout, do the default behavior. | ||
| 627 | if (String.IsNullOrEmpty(layout)) | ||
| 628 | { | ||
| 629 | if (String.IsNullOrEmpty(mediaLayoutDirectory)) | ||
| 630 | { | ||
| 631 | layout = layoutDirectory; | ||
| 632 | } | ||
| 633 | else if (Path.IsPathRooted(mediaLayoutDirectory)) | ||
| 634 | { | ||
| 635 | layout = mediaLayoutDirectory; | ||
| 636 | } | ||
| 637 | else | ||
| 638 | { | ||
| 639 | layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); | ||
| 640 | } | ||
| 641 | } | ||
| 642 | |||
| 643 | return layout; | ||
| 644 | } | ||
| 645 | } | ||
| 646 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs new file mode 100644 index 00000000..41da2a13 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs | |||
| @@ -0,0 +1,211 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Globalization; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Binds the summary information table of a database. | ||
| 14 | /// </summary> | ||
| 15 | internal class BindSummaryInfoCommand | ||
| 16 | { | ||
| 17 | public BindSummaryInfoCommand(IntermediateSection section, int? summaryInformationCodepage, string productLanguage, IBackendHelper backendHelper, IWixBranding branding) | ||
| 18 | { | ||
| 19 | this.Section = section; | ||
| 20 | this.SummaryInformationCodepage = summaryInformationCodepage; | ||
| 21 | this.ProductLanguage = productLanguage; | ||
| 22 | this.BackendHelper = backendHelper; | ||
| 23 | this.Branding = branding; | ||
| 24 | } | ||
| 25 | |||
| 26 | private IntermediateSection Section { get; } | ||
| 27 | |||
| 28 | private int? SummaryInformationCodepage { get; } | ||
| 29 | |||
| 30 | private string ProductLanguage { get; } | ||
| 31 | |||
| 32 | private IBackendHelper BackendHelper { get; } | ||
| 33 | |||
| 34 | private IWixBranding Branding { get; } | ||
| 35 | |||
| 36 | /// <summary> | ||
| 37 | /// Returns a flag indicating if files are compressed by default. | ||
| 38 | /// </summary> | ||
| 39 | public bool Compressed { get; private set; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Returns a flag indicating if uncompressed files use long filenames. | ||
| 43 | /// </summary> | ||
| 44 | public bool LongNames { get; private set; } | ||
| 45 | |||
| 46 | public int InstallerVersion { get; private set; } | ||
| 47 | |||
| 48 | public Platform Platform { get; private set; } | ||
| 49 | |||
| 50 | /// <summary> | ||
| 51 | /// Modularization guid, or null if the output is not a module. | ||
| 52 | /// </summary> | ||
| 53 | public string ModularizationSuffix { get; private set; } | ||
| 54 | |||
| 55 | public void Execute() | ||
| 56 | { | ||
| 57 | this.Compressed = false; | ||
| 58 | this.LongNames = false; | ||
| 59 | this.InstallerVersion = 0; | ||
| 60 | this.ModularizationSuffix = null; | ||
| 61 | |||
| 62 | SummaryInformationSymbol summaryInformationCodepageSymbol = null; | ||
| 63 | SummaryInformationSymbol platformAndLanguageSymbol = null; | ||
| 64 | var foundCreateDateTime = false; | ||
| 65 | var foundLastSaveDataTime = false; | ||
| 66 | var foundCreatingApplication = false; | ||
| 67 | var foundPackageCode = false; | ||
| 68 | var now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); | ||
| 69 | |||
| 70 | foreach (var summaryInformationSymbol in this.Section.Symbols.OfType<SummaryInformationSymbol>()) | ||
| 71 | { | ||
| 72 | switch (summaryInformationSymbol.PropertyId) | ||
| 73 | { | ||
| 74 | case SummaryInformationType.Codepage: // PID_CODEPAGE | ||
| 75 | summaryInformationCodepageSymbol = summaryInformationSymbol; | ||
| 76 | break; | ||
| 77 | |||
| 78 | case SummaryInformationType.PlatformAndLanguage: | ||
| 79 | platformAndLanguageSymbol = summaryInformationSymbol; | ||
| 80 | break; | ||
| 81 | |||
| 82 | case SummaryInformationType.PackageCode: // PID_REVNUMBER | ||
| 83 | foundPackageCode = true; | ||
| 84 | var packageCode = summaryInformationSymbol.Value; | ||
| 85 | |||
| 86 | if (SectionType.Module == this.Section.Type) | ||
| 87 | { | ||
| 88 | this.ModularizationSuffix = "." + packageCode.Substring(1, 36).Replace('-', '_'); | ||
| 89 | } | ||
| 90 | break; | ||
| 91 | case SummaryInformationType.Created: | ||
| 92 | foundCreateDateTime = true; | ||
| 93 | break; | ||
| 94 | case SummaryInformationType.LastSaved: | ||
| 95 | foundLastSaveDataTime = true; | ||
| 96 | break; | ||
| 97 | case SummaryInformationType.WindowsInstallerVersion: | ||
| 98 | this.InstallerVersion = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); | ||
| 99 | break; | ||
| 100 | case SummaryInformationType.WordCount: | ||
| 101 | if (SectionType.Patch == this.Section.Type) | ||
| 102 | { | ||
| 103 | this.LongNames = true; | ||
| 104 | this.Compressed = true; | ||
| 105 | } | ||
| 106 | else | ||
| 107 | { | ||
| 108 | var attributes = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); | ||
| 109 | this.LongNames = (0 == (attributes & 1)); | ||
| 110 | this.Compressed = (2 == (attributes & 2)); | ||
| 111 | } | ||
| 112 | break; | ||
| 113 | case SummaryInformationType.CreatingApplication: // PID_APPNAME | ||
| 114 | foundCreatingApplication = true; | ||
| 115 | break; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | // Ensure the codepage is set properly. | ||
| 120 | if (summaryInformationCodepageSymbol == null) | ||
| 121 | { | ||
| 122 | summaryInformationCodepageSymbol = this.Section.AddSymbol(new SummaryInformationSymbol(null) | ||
| 123 | { | ||
| 124 | PropertyId = SummaryInformationType.Codepage | ||
| 125 | }); | ||
| 126 | } | ||
| 127 | |||
| 128 | var codepage = summaryInformationCodepageSymbol.Value; | ||
| 129 | |||
| 130 | if (String.IsNullOrEmpty(codepage)) | ||
| 131 | { | ||
| 132 | codepage = this.SummaryInformationCodepage?.ToString(CultureInfo.InvariantCulture) ?? "1252"; | ||
| 133 | } | ||
| 134 | |||
| 135 | summaryInformationCodepageSymbol.Value = this.BackendHelper.GetValidCodePage(codepage, onlyAnsi: true).ToString(CultureInfo.InvariantCulture); | ||
| 136 | |||
| 137 | // Ensure the language is set properly and figure out what platform we are targeting. | ||
| 138 | if (platformAndLanguageSymbol != null) | ||
| 139 | { | ||
| 140 | this.Platform = EnsureLanguageAndGetPlatformFromSummaryInformation(platformAndLanguageSymbol, this.ProductLanguage); | ||
| 141 | } | ||
| 142 | |||
| 143 | // Set the revision number (package/patch code) if it should be automatically generated. | ||
| 144 | if (!foundPackageCode) | ||
| 145 | { | ||
| 146 | this.Section.AddSymbol(new SummaryInformationSymbol(null) | ||
| 147 | { | ||
| 148 | PropertyId = SummaryInformationType.PackageCode, | ||
| 149 | Value = this.BackendHelper.CreateGuid(), | ||
| 150 | }); | ||
| 151 | } | ||
| 152 | |||
| 153 | // add a summary information row for the create time/date property if its not already set | ||
| 154 | if (!foundCreateDateTime) | ||
| 155 | { | ||
| 156 | this.Section.AddSymbol(new SummaryInformationSymbol(null) | ||
| 157 | { | ||
| 158 | PropertyId = SummaryInformationType.Created, | ||
| 159 | Value = now, | ||
| 160 | }); | ||
| 161 | } | ||
| 162 | |||
| 163 | // add a summary information row for the last save time/date property if its not already set | ||
| 164 | if (!foundLastSaveDataTime) | ||
| 165 | { | ||
| 166 | this.Section.AddSymbol(new SummaryInformationSymbol(null) | ||
| 167 | { | ||
| 168 | PropertyId = SummaryInformationType.LastSaved, | ||
| 169 | Value = now, | ||
| 170 | }); | ||
| 171 | } | ||
| 172 | |||
| 173 | // add a summary information row for the creating application property if its not already set | ||
| 174 | if (!foundCreatingApplication) | ||
| 175 | { | ||
| 176 | this.Section.AddSymbol(new SummaryInformationSymbol(null) | ||
| 177 | { | ||
| 178 | PropertyId = SummaryInformationType.CreatingApplication, | ||
| 179 | Value = this.Branding.GetCreatingApplication(), | ||
| 180 | }); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | private static Platform EnsureLanguageAndGetPlatformFromSummaryInformation(SummaryInformationSymbol symbol, string language) | ||
| 185 | { | ||
| 186 | var value = symbol.Value; | ||
| 187 | var separatorIndex = value.IndexOf(';'); | ||
| 188 | var platformValue = separatorIndex > 0 ? value.Substring(0, separatorIndex) : value; | ||
| 189 | |||
| 190 | // If the language was provided and there was language value after the separator | ||
| 191 | // (or the separator was absent) then use the provided language. | ||
| 192 | if (!String.IsNullOrEmpty(language) && (separatorIndex < 0 || separatorIndex + 1 == value.Length)) | ||
| 193 | { | ||
| 194 | symbol.Value = platformValue + ';' + language; | ||
| 195 | } | ||
| 196 | |||
| 197 | switch (platformValue) | ||
| 198 | { | ||
| 199 | case "x64": | ||
| 200 | return Platform.X64; | ||
| 201 | |||
| 202 | case "Arm64": | ||
| 203 | return Platform.ARM64; | ||
| 204 | |||
| 205 | case "Intel": | ||
| 206 | default: | ||
| 207 | return Platform.X86; | ||
| 208 | } | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs new file mode 100644 index 00000000..3379ec5d --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs | |||
| @@ -0,0 +1,445 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Globalization; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Core.Native.Msi; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Data.WindowsInstaller; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class BindTransformCommand | ||
| 15 | { | ||
| 16 | public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions) | ||
| 17 | { | ||
| 18 | this.Messaging = messaging; | ||
| 19 | this.BackendHelper = backendHelper; | ||
| 20 | this.FileSystemManager = fileSystemManager; | ||
| 21 | this.IntermediateFolder = intermediateFolder; | ||
| 22 | this.Transform = transform; | ||
| 23 | this.OutputPath = outputPath; | ||
| 24 | this.TableDefinitions = tableDefinitions; | ||
| 25 | } | ||
| 26 | |||
| 27 | private IMessaging Messaging { get; } | ||
| 28 | |||
| 29 | private IBackendHelper BackendHelper { get; } | ||
| 30 | |||
| 31 | private FileSystemManager FileSystemManager { get; } | ||
| 32 | |||
| 33 | private TableDefinitionCollection TableDefinitions { get; } | ||
| 34 | |||
| 35 | private string IntermediateFolder { get; } | ||
| 36 | |||
| 37 | private WindowsInstallerData Transform { get; } | ||
| 38 | |||
| 39 | private string OutputPath { get; } | ||
| 40 | |||
| 41 | public void Execute() | ||
| 42 | { | ||
| 43 | var transformFlags = 0; | ||
| 44 | |||
| 45 | var targetOutput = new WindowsInstallerData(null); | ||
| 46 | var updatedOutput = new WindowsInstallerData(null); | ||
| 47 | |||
| 48 | // TODO: handle added columns | ||
| 49 | |||
| 50 | // to generate a localized transform, both the target and updated | ||
| 51 | // databases need to have the same code page. the only reason to | ||
| 52 | // set different code pages is to support localized primary key | ||
| 53 | // columns, but that would only support deleting rows. if this | ||
| 54 | // becomes necessary, define a PreviousCodepage property on the | ||
| 55 | // Output class and persist this throughout transform generation. | ||
| 56 | targetOutput.Codepage = this.Transform.Codepage; | ||
| 57 | updatedOutput.Codepage = this.Transform.Codepage; | ||
| 58 | |||
| 59 | // remove certain Property rows which will be populated from summary information values | ||
| 60 | string targetUpgradeCode = null; | ||
| 61 | string updatedUpgradeCode = null; | ||
| 62 | |||
| 63 | if (this.Transform.TryGetTable("Property", out var propertyTable)) | ||
| 64 | { | ||
| 65 | for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) | ||
| 66 | { | ||
| 67 | Row row = propertyTable.Rows[i]; | ||
| 68 | |||
| 69 | if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) | ||
| 70 | { | ||
| 71 | propertyTable.Rows.RemoveAt(i); | ||
| 72 | |||
| 73 | if ("UpgradeCode" == (string)row[0]) | ||
| 74 | { | ||
| 75 | updatedUpgradeCode = (string)row[1]; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | var targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 82 | var updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 83 | var targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
| 84 | var updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); | ||
| 85 | |||
| 86 | // process special summary information values | ||
| 87 | foreach (var row in this.Transform.Tables["_SummaryInformation"].Rows) | ||
| 88 | { | ||
| 89 | var summaryId = row.FieldAsInteger(0); | ||
| 90 | var summaryData = row.FieldAsString(1); | ||
| 91 | |||
| 92 | if ((int)SummaryInformation.Transform.CodePage == summaryId) | ||
| 93 | { | ||
| 94 | // convert from a web name if provided | ||
| 95 | var codePage = summaryData; | ||
| 96 | if (null == codePage) | ||
| 97 | { | ||
| 98 | codePage = "0"; | ||
| 99 | } | ||
| 100 | else | ||
| 101 | { | ||
| 102 | codePage = this.BackendHelper.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); | ||
| 103 | } | ||
| 104 | |||
| 105 | var previousCodePage = row.Fields[1].PreviousData; | ||
| 106 | if (null == previousCodePage) | ||
| 107 | { | ||
| 108 | previousCodePage = "0"; | ||
| 109 | } | ||
| 110 | else | ||
| 111 | { | ||
| 112 | previousCodePage = this.BackendHelper.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); | ||
| 113 | } | ||
| 114 | |||
| 115 | var targetCodePageRow = targetSummaryInfo.CreateRow(null); | ||
| 116 | targetCodePageRow[0] = 1; // PID_CODEPAGE | ||
| 117 | targetCodePageRow[1] = previousCodePage; | ||
| 118 | |||
| 119 | var updatedCodePageRow = updatedSummaryInfo.CreateRow(null); | ||
| 120 | updatedCodePageRow[0] = 1; // PID_CODEPAGE | ||
| 121 | updatedCodePageRow[1] = codePage; | ||
| 122 | } | ||
| 123 | else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId || | ||
| 124 | (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == summaryId) | ||
| 125 | { | ||
| 126 | // the target language | ||
| 127 | var propertyData = summaryData.Split(';'); | ||
| 128 | var lang = 2 == propertyData.Length ? propertyData[1] : "0"; | ||
| 129 | |||
| 130 | var tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetSummaryInfo : updatedSummaryInfo; | ||
| 131 | var tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetPropertyTable : updatedPropertyTable; | ||
| 132 | |||
| 133 | var productLanguageRow = tempPropertyTable.CreateRow(null); | ||
| 134 | productLanguageRow[0] = "ProductLanguage"; | ||
| 135 | productLanguageRow[1] = lang; | ||
| 136 | |||
| 137 | // set the platform;language on the MSI to be generated | ||
| 138 | var templateRow = tempSummaryInfo.CreateRow(null); | ||
| 139 | templateRow[0] = 7; // PID_TEMPLATE | ||
| 140 | templateRow[1] = summaryData; | ||
| 141 | } | ||
| 142 | else if ((int)SummaryInformation.Transform.ProductCodes == summaryId) | ||
| 143 | { | ||
| 144 | var propertyData = summaryData.Split(';'); | ||
| 145 | |||
| 146 | var targetProductCodeRow = targetPropertyTable.CreateRow(null); | ||
| 147 | targetProductCodeRow[0] = "ProductCode"; | ||
| 148 | targetProductCodeRow[1] = propertyData[0].Substring(0, 38); | ||
| 149 | |||
| 150 | var targetProductVersionRow = targetPropertyTable.CreateRow(null); | ||
| 151 | targetProductVersionRow[0] = "ProductVersion"; | ||
| 152 | targetProductVersionRow[1] = propertyData[0].Substring(38); | ||
| 153 | |||
| 154 | var updatedProductCodeRow = updatedPropertyTable.CreateRow(null); | ||
| 155 | updatedProductCodeRow[0] = "ProductCode"; | ||
| 156 | updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); | ||
| 157 | |||
| 158 | var updatedProductVersionRow = updatedPropertyTable.CreateRow(null); | ||
| 159 | updatedProductVersionRow[0] = "ProductVersion"; | ||
| 160 | updatedProductVersionRow[1] = propertyData[1].Substring(38); | ||
| 161 | |||
| 162 | // UpgradeCode is optional and may not exists in the target | ||
| 163 | // or upgraded databases, so do not include a null-valued | ||
| 164 | // UpgradeCode property. | ||
| 165 | |||
| 166 | targetUpgradeCode = propertyData[2]; | ||
| 167 | if (!String.IsNullOrEmpty(targetUpgradeCode)) | ||
| 168 | { | ||
| 169 | var targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); | ||
| 170 | targetUpgradeCodeRow[0] = "UpgradeCode"; | ||
| 171 | targetUpgradeCodeRow[1] = targetUpgradeCode; | ||
| 172 | |||
| 173 | // If the target UpgradeCode is specified, an updated | ||
| 174 | // UpgradeCode is required. | ||
| 175 | if (String.IsNullOrEmpty(updatedUpgradeCode)) | ||
| 176 | { | ||
| 177 | updatedUpgradeCode = targetUpgradeCode; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | if (!String.IsNullOrEmpty(updatedUpgradeCode)) | ||
| 182 | { | ||
| 183 | var updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); | ||
| 184 | updatedUpgradeCodeRow[0] = "UpgradeCode"; | ||
| 185 | updatedUpgradeCodeRow[1] = updatedUpgradeCode; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | else if ((int)SummaryInformation.Transform.ValidationFlags == summaryId) | ||
| 189 | { | ||
| 190 | transformFlags = Convert.ToInt32(summaryData, CultureInfo.InvariantCulture); | ||
| 191 | } | ||
| 192 | else if ((int)SummaryInformation.Transform.Reserved11 == summaryId) | ||
| 193 | { | ||
| 194 | // PID_LASTPRINTED should be null for transforms | ||
| 195 | row.Operation = RowOperation.None; | ||
| 196 | } | ||
| 197 | else | ||
| 198 | { | ||
| 199 | // add everything else as is | ||
| 200 | var targetRow = targetSummaryInfo.CreateRow(null); | ||
| 201 | targetRow[0] = row[0]; | ||
| 202 | targetRow[1] = row[1]; | ||
| 203 | |||
| 204 | var updatedRow = updatedSummaryInfo.CreateRow(null); | ||
| 205 | updatedRow[0] = row[0]; | ||
| 206 | updatedRow[1] = row[1]; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | // Validate that both databases have an UpgradeCode if the | ||
| 211 | // authoring transform will validate the UpgradeCode; otherwise, | ||
| 212 | // MsiCreateTransformSummaryinfo() will fail with 1620. | ||
| 213 | if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && | ||
| 214 | (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) | ||
| 215 | { | ||
| 216 | this.Messaging.Write(ErrorMessages.BothUpgradeCodesRequired()); | ||
| 217 | } | ||
| 218 | |||
| 219 | string emptyFile = null; | ||
| 220 | |||
| 221 | foreach (var table in this.Transform.Tables) | ||
| 222 | { | ||
| 223 | // Ignore unreal tables when building transforms except the _Stream table. | ||
| 224 | // These tables are ignored when generating the database so there is no reason | ||
| 225 | // to process them here. | ||
| 226 | if (table.Definition.Unreal && "_Streams" != table.Name) | ||
| 227 | { | ||
| 228 | continue; | ||
| 229 | } | ||
| 230 | |||
| 231 | // process table operations | ||
| 232 | switch (table.Operation) | ||
| 233 | { | ||
| 234 | case TableOperation.Add: | ||
| 235 | updatedOutput.EnsureTable(table.Definition); | ||
| 236 | break; | ||
| 237 | case TableOperation.Drop: | ||
| 238 | targetOutput.EnsureTable(table.Definition); | ||
| 239 | continue; | ||
| 240 | default: | ||
| 241 | targetOutput.EnsureTable(table.Definition); | ||
| 242 | updatedOutput.EnsureTable(table.Definition); | ||
| 243 | break; | ||
| 244 | } | ||
| 245 | |||
| 246 | // process row operations | ||
| 247 | foreach (var row in table.Rows) | ||
| 248 | { | ||
| 249 | switch (row.Operation) | ||
| 250 | { | ||
| 251 | case RowOperation.Add: | ||
| 252 | var updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
| 253 | updatedTable.Rows.Add(row); | ||
| 254 | continue; | ||
| 255 | |||
| 256 | case RowOperation.Delete: | ||
| 257 | var targetTable = targetOutput.EnsureTable(table.Definition); | ||
| 258 | targetTable.Rows.Add(row); | ||
| 259 | |||
| 260 | // fill-in non-primary key values | ||
| 261 | foreach (var field in row.Fields) | ||
| 262 | { | ||
| 263 | if (!field.Column.PrimaryKey) | ||
| 264 | { | ||
| 265 | if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) | ||
| 266 | { | ||
| 267 | field.Data = field.Column.MinValue; | ||
| 268 | } | ||
| 269 | else if (ColumnType.Object == field.Column.Type) | ||
| 270 | { | ||
| 271 | if (null == emptyFile) | ||
| 272 | { | ||
| 273 | emptyFile = Path.Combine(this.IntermediateFolder, "empty"); | ||
| 274 | } | ||
| 275 | |||
| 276 | field.Data = emptyFile; | ||
| 277 | } | ||
| 278 | else | ||
| 279 | { | ||
| 280 | field.Data = "0"; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | } | ||
| 284 | continue; | ||
| 285 | } | ||
| 286 | |||
| 287 | // Assure that the file table's sequence is populated | ||
| 288 | if ("File" == table.Name) | ||
| 289 | { | ||
| 290 | foreach (var fileRow in table.Rows) | ||
| 291 | { | ||
| 292 | if (null == fileRow[7]) | ||
| 293 | { | ||
| 294 | if (RowOperation.Add == fileRow.Operation) | ||
| 295 | { | ||
| 296 | this.Messaging.Write(ErrorMessages.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); | ||
| 297 | break; | ||
| 298 | } | ||
| 299 | |||
| 300 | // Set to 1 to prevent invalid IDT file from being generated | ||
| 301 | fileRow[7] = 1; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | // process modified and unmodified rows | ||
| 307 | var modifiedRow = false; | ||
| 308 | var targetRow = table.Definition.CreateRow(null); | ||
| 309 | var updatedRow = row; | ||
| 310 | for (var i = 0; i < row.Fields.Length; i++) | ||
| 311 | { | ||
| 312 | var updatedField = row.Fields[i]; | ||
| 313 | |||
| 314 | if (updatedField.Modified) | ||
| 315 | { | ||
| 316 | // set a different value in the target row to ensure this value will be modified during transform generation | ||
| 317 | if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) | ||
| 318 | { | ||
| 319 | var data = updatedField.AsNullableInteger(); | ||
| 320 | targetRow[i] = (data == 1) ? 2 : 1; | ||
| 321 | } | ||
| 322 | else if (ColumnType.Object == updatedField.Column.Type) | ||
| 323 | { | ||
| 324 | if (null == emptyFile) | ||
| 325 | { | ||
| 326 | emptyFile = Path.Combine(this.IntermediateFolder, "empty"); | ||
| 327 | } | ||
| 328 | |||
| 329 | targetRow[i] = emptyFile; | ||
| 330 | } | ||
| 331 | else | ||
| 332 | { | ||
| 333 | var data = updatedField.AsString(); | ||
| 334 | targetRow[i] = (data == "0") ? "1" : "0"; | ||
| 335 | } | ||
| 336 | |||
| 337 | modifiedRow = true; | ||
| 338 | } | ||
| 339 | else if (ColumnType.Object == updatedField.Column.Type) | ||
| 340 | { | ||
| 341 | var objectField = (ObjectField)updatedField; | ||
| 342 | |||
| 343 | // create an empty file for comparing against | ||
| 344 | if (null == objectField.PreviousData) | ||
| 345 | { | ||
| 346 | if (null == emptyFile) | ||
| 347 | { | ||
| 348 | emptyFile = Path.Combine(this.IntermediateFolder, "empty"); | ||
| 349 | } | ||
| 350 | |||
| 351 | targetRow[i] = emptyFile; | ||
| 352 | modifiedRow = true; | ||
| 353 | } | ||
| 354 | else if (!this.FileSystemManager.CompareFiles(objectField.PreviousData, (string)objectField.Data)) | ||
| 355 | { | ||
| 356 | targetRow[i] = objectField.PreviousData; | ||
| 357 | modifiedRow = true; | ||
| 358 | } | ||
| 359 | } | ||
| 360 | else // unmodified | ||
| 361 | { | ||
| 362 | if (null != updatedField.Data) | ||
| 363 | { | ||
| 364 | targetRow[i] = updatedField.Data; | ||
| 365 | } | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | // modified rows and certain special rows go in the target and updated msi databases | ||
| 370 | if (modifiedRow || | ||
| 371 | ("Property" == table.Name && | ||
| 372 | ("ProductCode" == (string)row[0] || | ||
| 373 | "ProductLanguage" == (string)row[0] || | ||
| 374 | "ProductVersion" == (string)row[0] || | ||
| 375 | "UpgradeCode" == (string)row[0]))) | ||
| 376 | { | ||
| 377 | var targetTable = targetOutput.EnsureTable(table.Definition); | ||
| 378 | targetTable.Rows.Add(targetRow); | ||
| 379 | |||
| 380 | var updatedTable = updatedOutput.EnsureTable(table.Definition); | ||
| 381 | updatedTable.Rows.Add(updatedRow); | ||
| 382 | } | ||
| 383 | } | ||
| 384 | } | ||
| 385 | |||
| 386 | //foreach (BinderExtension extension in this.Extensions) | ||
| 387 | //{ | ||
| 388 | // extension.PostBind(this.Context); | ||
| 389 | //} | ||
| 390 | |||
| 391 | // Any errors encountered up to this point can cause errors during generation. | ||
| 392 | if (this.Messaging.EncounteredError) | ||
| 393 | { | ||
| 394 | return; | ||
| 395 | } | ||
| 396 | |||
| 397 | var transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); | ||
| 398 | var targetDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_target.msi")); | ||
| 399 | var updatedDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_updated.msi")); | ||
| 400 | |||
| 401 | try | ||
| 402 | { | ||
| 403 | if (!String.IsNullOrEmpty(emptyFile)) | ||
| 404 | { | ||
| 405 | using (var fileStream = File.Create(emptyFile)) | ||
| 406 | { | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false); | ||
| 411 | this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true); | ||
| 412 | |||
| 413 | // make sure the directory exists | ||
| 414 | Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); | ||
| 415 | |||
| 416 | // create the transform file | ||
| 417 | using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) | ||
| 418 | using (var updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) | ||
| 419 | { | ||
| 420 | if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) | ||
| 421 | { | ||
| 422 | updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); | ||
| 423 | } | ||
| 424 | else | ||
| 425 | { | ||
| 426 | this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); | ||
| 427 | } | ||
| 428 | } | ||
| 429 | } | ||
| 430 | finally | ||
| 431 | { | ||
| 432 | if (!String.IsNullOrEmpty(emptyFile)) | ||
| 433 | { | ||
| 434 | File.Delete(emptyFile); | ||
| 435 | } | ||
| 436 | } | ||
| 437 | } | ||
| 438 | |||
| 439 | private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns) | ||
| 440 | { | ||
| 441 | var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true); | ||
| 442 | command.Execute(); | ||
| 443 | } | ||
| 444 | } | ||
| 445 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs new file mode 100644 index 00000000..13b079ad --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs | |||
| @@ -0,0 +1,171 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Threading; | ||
| 10 | using WixToolset.Core.Native; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple | ||
| 16 | /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished. | ||
| 17 | /// </summary> | ||
| 18 | internal sealed class CabinetBuilder | ||
| 19 | { | ||
| 20 | private readonly Queue<CabinetWorkItem> cabinetWorkItems; | ||
| 21 | private int threadCount; | ||
| 22 | |||
| 23 | // Address of Binder's callback function for Cabinet Splitting | ||
| 24 | private readonly IntPtr newCabNamesCallBackAddress; | ||
| 25 | |||
| 26 | /// <summary> | ||
| 27 | /// Instantiate a new CabinetBuilder. | ||
| 28 | /// </summary> | ||
| 29 | /// <param name="messaging"></param> | ||
| 30 | /// <param name="threadCount">number of threads to use</param> | ||
| 31 | /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param> | ||
| 32 | public CabinetBuilder(IMessaging messaging, int threadCount, IntPtr newCabNamesCallBackAddress) | ||
| 33 | { | ||
| 34 | if (0 >= threadCount) | ||
| 35 | { | ||
| 36 | throw new ArgumentOutOfRangeException(nameof(threadCount)); | ||
| 37 | } | ||
| 38 | |||
| 39 | this.cabinetWorkItems = new Queue<CabinetWorkItem>(); | ||
| 40 | this.Messaging = messaging; | ||
| 41 | this.threadCount = threadCount; | ||
| 42 | |||
| 43 | // Set Address of Binder's callback function for Cabinet Splitting | ||
| 44 | this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; | ||
| 45 | } | ||
| 46 | |||
| 47 | private IMessaging Messaging { get; } | ||
| 48 | |||
| 49 | public int MaximumCabinetSizeForLargeFileSplitting { get; set; } | ||
| 50 | |||
| 51 | public int MaximumUncompressedMediaSize { get; set; } | ||
| 52 | |||
| 53 | /// <summary> | ||
| 54 | /// Enqueues a CabinetWorkItem to the queue. | ||
| 55 | /// </summary> | ||
| 56 | /// <param name="cabinetWorkItem">cabinet work item</param> | ||
| 57 | public void Enqueue(CabinetWorkItem cabinetWorkItem) => this.cabinetWorkItems.Enqueue(cabinetWorkItem); | ||
| 58 | |||
| 59 | /// <summary> | ||
| 60 | /// Create the queued cabinets. | ||
| 61 | /// </summary> | ||
| 62 | /// <returns>error message number (zero if no error)</returns> | ||
| 63 | public void CreateQueuedCabinets() | ||
| 64 | { | ||
| 65 | // don't create more threads than the number of cabinets to build | ||
| 66 | var numberOfThreads = Math.Min(this.threadCount, this.cabinetWorkItems.Count); | ||
| 67 | |||
| 68 | if (0 < numberOfThreads) | ||
| 69 | { | ||
| 70 | var threads = new Thread[numberOfThreads]; | ||
| 71 | |||
| 72 | for (var i = 0; i < threads.Length; i++) | ||
| 73 | { | ||
| 74 | threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); | ||
| 75 | threads[i].Start(); | ||
| 76 | } | ||
| 77 | |||
| 78 | // wait for all threads to finish | ||
| 79 | foreach (var thread in threads) | ||
| 80 | { | ||
| 81 | thread.Join(); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | /// <summary> | ||
| 87 | /// This function gets called by multiple threads to do actual work. | ||
| 88 | /// It takes one work item at a time and calls this.CreateCabinet(). | ||
| 89 | /// It does not return until cabinetWorkItems queue is empty | ||
| 90 | /// </summary> | ||
| 91 | private void ProcessWorkItems() | ||
| 92 | { | ||
| 93 | try | ||
| 94 | { | ||
| 95 | while (true) | ||
| 96 | { | ||
| 97 | CabinetWorkItem cabinetWorkItem; | ||
| 98 | |||
| 99 | lock (this.cabinetWorkItems) | ||
| 100 | { | ||
| 101 | // check if there are any more cabinets to create | ||
| 102 | if (0 == this.cabinetWorkItems.Count) | ||
| 103 | { | ||
| 104 | break; | ||
| 105 | } | ||
| 106 | |||
| 107 | cabinetWorkItem = this.cabinetWorkItems.Dequeue(); | ||
| 108 | } | ||
| 109 | |||
| 110 | // create a cabinet | ||
| 111 | this.CreateCabinet(cabinetWorkItem); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | catch (WixException we) | ||
| 115 | { | ||
| 116 | this.Messaging.Write(we.Error); | ||
| 117 | } | ||
| 118 | catch (Exception e) | ||
| 119 | { | ||
| 120 | this.Messaging.Write(ErrorMessages.UnexpectedException(e)); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | /// <summary> | ||
| 125 | /// Creates a cabinet using the wixcab.dll interop layer. | ||
| 126 | /// </summary> | ||
| 127 | /// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param> | ||
| 128 | private void CreateCabinet(CabinetWorkItem cabinetWorkItem) | ||
| 129 | { | ||
| 130 | this.Messaging.Write(VerboseMessages.CreateCabinet(cabinetWorkItem.CabinetFile)); | ||
| 131 | |||
| 132 | var maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting | ||
| 133 | ulong maxPreCompressedSizeInBytes = 0; | ||
| 134 | |||
| 135 | if (this.MaximumCabinetSizeForLargeFileSplitting != 0) | ||
| 136 | { | ||
| 137 | // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize | ||
| 138 | // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file | ||
| 139 | if (1 == cabinetWorkItem.FileFacades.Count()) | ||
| 140 | { | ||
| 141 | // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs | ||
| 142 | // Get the Value for Max Uncompressed Media Size | ||
| 143 | maxPreCompressedSizeInBytes = (ulong)this.MaximumUncompressedMediaSize * 1024 * 1024; | ||
| 144 | |||
| 145 | var facade = cabinetWorkItem.FileFacades.First(); | ||
| 146 | |||
| 147 | // If the file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting | ||
| 148 | if ((ulong)facade.FileSize >= maxPreCompressedSizeInBytes) | ||
| 149 | { | ||
| 150 | maxCabinetSize = this.MaximumCabinetSizeForLargeFileSplitting; | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | // create the cabinet file | ||
| 156 | var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile); | ||
| 157 | |||
| 158 | var files = cabinetWorkItem.FileFacades | ||
| 159 | .OrderBy(f => f.Sequence) | ||
| 160 | .Select(facade => facade.Hash == null ? | ||
| 161 | new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix) : | ||
| 162 | new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4)) | ||
| 163 | .ToList(); | ||
| 164 | |||
| 165 | var cab = new Cabinet(cabinetPath); | ||
| 166 | cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); | ||
| 167 | |||
| 168 | // TODO: Handle newCabNamesCallBackAddress from compression. | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs new file mode 100644 index 00000000..875b46c2 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs | |||
| @@ -0,0 +1,132 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Core.Native; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Extensibility; | ||
| 12 | using WixToolset.Extensibility.Data; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | internal class CabinetResolver | ||
| 16 | { | ||
| 17 | public CabinetResolver(IServiceProvider serviceProvider, string cabCachePath, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions) | ||
| 18 | { | ||
| 19 | this.ServiceProvider = serviceProvider; | ||
| 20 | |||
| 21 | this.CabCachePath = cabCachePath; | ||
| 22 | |||
| 23 | this.BackendExtensions = backendExtensions; | ||
| 24 | } | ||
| 25 | |||
| 26 | private IServiceProvider ServiceProvider { get; } | ||
| 27 | |||
| 28 | private string CabCachePath { get; } | ||
| 29 | |||
| 30 | private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; } | ||
| 31 | |||
| 32 | public IResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<IFileFacade> fileFacades) | ||
| 33 | { | ||
| 34 | var filesWithPath = fileFacades.Select(this.CreateBindFileWithPath).ToList(); | ||
| 35 | |||
| 36 | IResolvedCabinet resolved = null; | ||
| 37 | |||
| 38 | foreach (var extension in this.BackendExtensions) | ||
| 39 | { | ||
| 40 | resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); | ||
| 41 | |||
| 42 | if (null != resolved) | ||
| 43 | { | ||
| 44 | return resolved; | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | // By default cabinet should be built and moved to the suggested location. | ||
| 49 | resolved = this.ServiceProvider.GetService<IResolvedCabinet>(); | ||
| 50 | resolved.BuildOption = CabinetBuildOption.BuildAndMove; | ||
| 51 | resolved.Path = cabinetPath; | ||
| 52 | |||
| 53 | // If a cabinet cache path was provided, change the location for the cabinet | ||
| 54 | // to be built to and check if there is a cabinet that can be reused. | ||
| 55 | if (!String.IsNullOrEmpty(this.CabCachePath)) | ||
| 56 | { | ||
| 57 | var cabinetName = Path.GetFileName(cabinetPath); | ||
| 58 | resolved.Path = Path.Combine(this.CabCachePath, cabinetName); | ||
| 59 | |||
| 60 | if (CheckFileExists(resolved.Path)) | ||
| 61 | { | ||
| 62 | // Assume that none of the following are true: | ||
| 63 | // 1. any files are added or removed | ||
| 64 | // 2. order of files changed or names changed | ||
| 65 | // 3. modified time changed | ||
| 66 | var cabinetValid = true; | ||
| 67 | |||
| 68 | var cabinet = new Cabinet(resolved.Path); | ||
| 69 | var fileList = cabinet.Enumerate(); | ||
| 70 | |||
| 71 | if (filesWithPath.Count() != fileList.Count) | ||
| 72 | { | ||
| 73 | cabinetValid = false; | ||
| 74 | } | ||
| 75 | else | ||
| 76 | { | ||
| 77 | var i = 0; | ||
| 78 | foreach (var file in filesWithPath) | ||
| 79 | { | ||
| 80 | // First check that the file identifiers match because that is quick and easy. | ||
| 81 | var cabFileInfo = fileList[i]; | ||
| 82 | cabinetValid = (cabFileInfo.FileId == file.Id); | ||
| 83 | if (cabinetValid) | ||
| 84 | { | ||
| 85 | // Still valid so ensure the file sizes are the same. | ||
| 86 | var fileInfo = new FileInfo(file.Path); | ||
| 87 | cabinetValid = (cabFileInfo.Size == fileInfo.Length); | ||
| 88 | if (cabinetValid) | ||
| 89 | { | ||
| 90 | // Still valid so ensure the source time stamp hasn't changed. | ||
| 91 | cabinetValid = cabFileInfo.SameAsDateTime(fileInfo.LastWriteTime); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | if (!cabinetValid) | ||
| 96 | { | ||
| 97 | break; | ||
| 98 | } | ||
| 99 | |||
| 100 | i++; | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | return resolved; | ||
| 109 | } | ||
| 110 | |||
| 111 | private IBindFileWithPath CreateBindFileWithPath(IFileFacade facade) | ||
| 112 | { | ||
| 113 | var result = this.ServiceProvider.GetService<IBindFileWithPath>(); | ||
| 114 | result.Id = facade.Id; | ||
| 115 | result.Path = facade.SourcePath; | ||
| 116 | |||
| 117 | return result; | ||
| 118 | } | ||
| 119 | |||
| 120 | private static bool CheckFileExists(string path) | ||
| 121 | { | ||
| 122 | try | ||
| 123 | { | ||
| 124 | return File.Exists(path); | ||
| 125 | } | ||
| 126 | catch (ArgumentException) | ||
| 127 | { | ||
| 128 | throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs new file mode 100644 index 00000000..1990ea78 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Extensibility.Data; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// A cabinet builder work item. | ||
| 11 | /// </summary> | ||
| 12 | internal sealed class CabinetWorkItem | ||
| 13 | { | ||
| 14 | /// <summary> | ||
| 15 | /// Instantiate a new CabinetWorkItem. | ||
| 16 | /// </summary> | ||
| 17 | /// <param name="fileFacades">The collection of files in this cabinet.</param> | ||
| 18 | /// <param name="cabinetFile">The cabinet file.</param> | ||
| 19 | /// <param name="maxThreshold">Maximum threshold for each cabinet.</param> | ||
| 20 | /// <param name="compressionLevel">The compression level of the cabinet.</param> | ||
| 21 | /// <param name="modularizationSuffix">Modularization suffix used when building a Merge Module.</param> | ||
| 22 | /// <!--<param name="binderFileManager">The binder file manager.</param>--> | ||
| 23 | public CabinetWorkItem(IEnumerable<IFileFacade> fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix /*, BinderFileManager binderFileManager*/) | ||
| 24 | { | ||
| 25 | this.CabinetFile = cabinetFile; | ||
| 26 | this.CompressionLevel = compressionLevel; | ||
| 27 | this.ModularizationSuffix = modularizationSuffix; | ||
| 28 | this.FileFacades = fileFacades; | ||
| 29 | //this.BinderFileManager = binderFileManager; | ||
| 30 | this.MaxThreshold = maxThreshold; | ||
| 31 | } | ||
| 32 | |||
| 33 | /// <summary> | ||
| 34 | /// Gets the cabinet file. | ||
| 35 | /// </summary> | ||
| 36 | /// <value>The cabinet file.</value> | ||
| 37 | public string CabinetFile { get; } | ||
| 38 | |||
| 39 | /// <summary> | ||
| 40 | /// Gets the compression level of the cabinet. | ||
| 41 | /// </summary> | ||
| 42 | /// <value>The compression level of the cabinet.</value> | ||
| 43 | public CompressionLevel CompressionLevel { get; } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Gets the modularization suffix used when building a Merge Module. | ||
| 47 | /// </summary> | ||
| 48 | public string ModularizationSuffix { get; } | ||
| 49 | |||
| 50 | /// <summary> | ||
| 51 | /// Gets the collection of files in this cabinet. | ||
| 52 | /// </summary> | ||
| 53 | /// <value>The collection of files in this cabinet.</value> | ||
| 54 | public IEnumerable<IFileFacade> FileFacades { get; } | ||
| 55 | |||
| 56 | // <summary> | ||
| 57 | // Gets the binder file manager. | ||
| 58 | // </summary> | ||
| 59 | // <value>The binder file manager.</value> | ||
| 60 | //public BinderFileManager BinderFileManager { get; private set; } | ||
| 61 | |||
| 62 | /// <summary> | ||
| 63 | /// Gets the max threshold. | ||
| 64 | /// </summary> | ||
| 65 | /// <value>The maximum threshold for a folder in a cabinet.</value> | ||
| 66 | public int MaxThreshold { get; } | ||
| 67 | } | ||
| 68 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs new file mode 100644 index 00000000..83a4949e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs | |||
| @@ -0,0 +1,455 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Runtime.InteropServices; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Data.WindowsInstaller; | ||
| 14 | using WixToolset.Extensibility; | ||
| 15 | using WixToolset.Extensibility.Data; | ||
| 16 | using WixToolset.Extensibility.Services; | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// Creates cabinet files. | ||
| 20 | /// </summary> | ||
| 21 | internal class CreateCabinetsCommand | ||
| 22 | { | ||
| 23 | public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB | ||
| 24 | public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) | ||
| 25 | |||
| 26 | private readonly List<IFileTransfer> fileTransfers; | ||
| 27 | |||
| 28 | private readonly List<ITrackedFile> trackedFiles; | ||
| 29 | |||
| 30 | private readonly FileSplitCabNamesCallback newCabNamesCallBack; | ||
| 31 | |||
| 32 | private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence | ||
| 33 | |||
| 34 | public CreateCabinetsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, WixMediaTemplateSymbol mediaTemplate) | ||
| 35 | { | ||
| 36 | this.fileTransfers = new List<IFileTransfer>(); | ||
| 37 | |||
| 38 | this.trackedFiles = new List<ITrackedFile>(); | ||
| 39 | |||
| 40 | this.newCabNamesCallBack = this.NewCabNamesCallBack; | ||
| 41 | |||
| 42 | this.ServiceProvider = serviceProvider; | ||
| 43 | |||
| 44 | this.BackendHelper = backendHelper; | ||
| 45 | |||
| 46 | this.MediaTemplate = mediaTemplate; | ||
| 47 | } | ||
| 48 | |||
| 49 | private IServiceProvider ServiceProvider { get; } | ||
| 50 | |||
| 51 | private IBackendHelper BackendHelper { get; } | ||
| 52 | |||
| 53 | private WixMediaTemplateSymbol MediaTemplate { get; } | ||
| 54 | |||
| 55 | /// <summary> | ||
| 56 | /// Sets the number of threads to use for cabinet creation. | ||
| 57 | /// </summary> | ||
| 58 | public int CabbingThreadCount { private get; set; } | ||
| 59 | |||
| 60 | public string CabCachePath { private get; set; } | ||
| 61 | |||
| 62 | public IMessaging Messaging { private get; set; } | ||
| 63 | |||
| 64 | public string IntermediateFolder { private get; set; } | ||
| 65 | |||
| 66 | /// <summary> | ||
| 67 | /// Sets the default compression level to use for cabinets | ||
| 68 | /// that don't have their compression level explicitly set. | ||
| 69 | /// </summary> | ||
| 70 | public CompressionLevel? DefaultCompressionLevel { private get; set; } | ||
| 71 | |||
| 72 | public IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { private get; set; } | ||
| 73 | |||
| 74 | public WindowsInstallerData Data { private get; set; } | ||
| 75 | |||
| 76 | public string LayoutDirectory { private get; set; } | ||
| 77 | |||
| 78 | public bool Compressed { private get; set; } | ||
| 79 | |||
| 80 | public string ModularizationSuffix { private get; set; } | ||
| 81 | |||
| 82 | public Dictionary<MediaSymbol, IEnumerable<IFileFacade>> FileFacadesByCabinet { private get; set; } | ||
| 83 | |||
| 84 | public Func<MediaSymbol, string, string, string> ResolveMedia { private get; set; } | ||
| 85 | |||
| 86 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 87 | |||
| 88 | public IEnumerable<IFileTransfer> FileTransfers => this.fileTransfers; | ||
| 89 | |||
| 90 | public IEnumerable<ITrackedFile> TrackedFiles => this.trackedFiles; | ||
| 91 | |||
| 92 | public void Execute() | ||
| 93 | { | ||
| 94 | this.lastCabinetAddedToMediaTable = new Dictionary<string, string>(); | ||
| 95 | |||
| 96 | // If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors. | ||
| 97 | if (this.CabbingThreadCount <= 0) | ||
| 98 | { | ||
| 99 | this.CabbingThreadCount = this.CalculateCabbingThreadCount(); | ||
| 100 | |||
| 101 | this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); | ||
| 102 | } | ||
| 103 | |||
| 104 | // Send Binder object to Facilitate NewCabNamesCallBack Callback | ||
| 105 | var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); | ||
| 106 | |||
| 107 | // Supply Compile MediaTemplate Attributes to Cabinet Builder | ||
| 108 | this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize); | ||
| 109 | cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting; | ||
| 110 | cabinetBuilder.MaximumUncompressedMediaSize = maximumUncompressedMediaSize; | ||
| 111 | |||
| 112 | foreach (var entry in this.FileFacadesByCabinet) | ||
| 113 | { | ||
| 114 | var mediaSymbol = entry.Key; | ||
| 115 | var files = entry.Value; | ||
| 116 | var compressionLevel = mediaSymbol.CompressionLevel ?? this.DefaultCompressionLevel ?? CompressionLevel.Medium; | ||
| 117 | var cabinetDir = this.ResolveMedia(mediaSymbol, mediaSymbol.Layout, this.LayoutDirectory); | ||
| 118 | |||
| 119 | var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files); | ||
| 120 | if (null != cabinetWorkItem) | ||
| 121 | { | ||
| 122 | cabinetBuilder.Enqueue(cabinetWorkItem); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | // stop processing if an error previously occurred | ||
| 127 | if (this.Messaging.EncounteredError) | ||
| 128 | { | ||
| 129 | return; | ||
| 130 | } | ||
| 131 | |||
| 132 | // create queued cabinets with multiple threads | ||
| 133 | cabinetBuilder.CreateQueuedCabinets(); | ||
| 134 | if (this.Messaging.EncounteredError) | ||
| 135 | { | ||
| 136 | return; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | private int CalculateCabbingThreadCount() | ||
| 141 | { | ||
| 142 | var cabbingThreadCount = Environment.ProcessorCount; | ||
| 143 | |||
| 144 | if (cabbingThreadCount <= 0) | ||
| 145 | { | ||
| 146 | cabbingThreadCount = 1; // reset to 1 when the environment variable is invalid. | ||
| 147 | |||
| 148 | this.Messaging.Write(WarningMessages.InvalidEnvironmentVariable("NUMBER_OF_PROCESSORS", Environment.ProcessorCount.ToString(), cabbingThreadCount.ToString())); | ||
| 149 | } | ||
| 150 | |||
| 151 | return cabbingThreadCount; | ||
| 152 | } | ||
| 153 | |||
| 154 | /// <summary> | ||
| 155 | /// Creates a work item to create a cabinet. | ||
| 156 | /// </summary> | ||
| 157 | /// <param name="data">Windows Installer data for the current database.</param> | ||
| 158 | /// <param name="cabinetDir">Directory to create cabinet in.</param> | ||
| 159 | /// <param name="mediaSymbol">Media symbol containing information about the cabinet.</param> | ||
| 160 | /// <param name="compressionLevel">Desired compression level.</param> | ||
| 161 | /// <param name="fileFacades">Collection of files in this cabinet.</param> | ||
| 162 | /// <returns>created CabinetWorkItem object</returns> | ||
| 163 | private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable<IFileFacade> fileFacades) | ||
| 164 | { | ||
| 165 | CabinetWorkItem cabinetWorkItem = null; | ||
| 166 | var tempCabinetFileX = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet); | ||
| 167 | |||
| 168 | // check for an empty cabinet | ||
| 169 | if (!fileFacades.Any()) | ||
| 170 | { | ||
| 171 | // Remove the leading '#' from the embedded cabinet name to make the warning easier to understand | ||
| 172 | var cabinetName = mediaSymbol.Cabinet.TrimStart('#'); | ||
| 173 | |||
| 174 | // If building a patch, remind them to run -p for torch. | ||
| 175 | if (OutputType.Patch == data.Type) | ||
| 176 | { | ||
| 177 | this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, true)); | ||
| 178 | } | ||
| 179 | else | ||
| 180 | { | ||
| 181 | this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName)); | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | var cabinetResolver = new CabinetResolver(this.ServiceProvider, this.CabCachePath, this.BackendExtensions); | ||
| 186 | |||
| 187 | var resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades); | ||
| 188 | |||
| 189 | // create a cabinet work item if it's not being skipped | ||
| 190 | if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) | ||
| 191 | { | ||
| 192 | // Default to the threshold for best smartcabbing (makes smallest cabinet). | ||
| 193 | cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold: 0, compressionLevel, this.ModularizationSuffix /*, this.FileManager*/); | ||
| 194 | } | ||
| 195 | else // reuse the cabinet from the cabinet cache. | ||
| 196 | { | ||
| 197 | this.Messaging.Write(VerboseMessages.ReusingCabCache(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet, resolvedCabinet.Path)); | ||
| 198 | |||
| 199 | try | ||
| 200 | { | ||
| 201 | // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The | ||
| 202 | // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that | ||
| 203 | // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from | ||
| 204 | // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output) | ||
| 205 | // causing the project to look like it perpetually needs a rebuild until all of the reused | ||
| 206 | // cabinets get newer timestamps. | ||
| 207 | File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now); | ||
| 208 | } | ||
| 209 | catch (Exception e) | ||
| 210 | { | ||
| 211 | this.Messaging.Write(WarningMessages.CannotUpdateCabCache(mediaSymbol.SourceLineNumbers, resolvedCabinet.Path, e.Message)); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | var trackResolvedCabinet = this.BackendHelper.TrackFile(resolvedCabinet.Path, TrackedFileType.Intermediate, mediaSymbol.SourceLineNumbers); | ||
| 216 | this.trackedFiles.Add(trackResolvedCabinet); | ||
| 217 | |||
| 218 | if (mediaSymbol.Cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
| 219 | { | ||
| 220 | var streamsTable = data.EnsureTable(this.TableDefinitions["_Streams"]); | ||
| 221 | |||
| 222 | var streamRow = streamsTable.CreateRow(mediaSymbol.SourceLineNumbers); | ||
| 223 | streamRow[0] = mediaSymbol.Cabinet.Substring(1); | ||
| 224 | streamRow[1] = resolvedCabinet.Path; | ||
| 225 | } | ||
| 226 | else | ||
| 227 | { | ||
| 228 | var trackDestination = this.BackendHelper.TrackFile(Path.Combine(cabinetDir, mediaSymbol.Cabinet), TrackedFileType.Final, mediaSymbol.SourceLineNumbers); | ||
| 229 | this.trackedFiles.Add(trackDestination); | ||
| 230 | |||
| 231 | var transfer = this.BackendHelper.CreateFileTransfer(resolvedCabinet.Path, trackDestination.Path, resolvedCabinet.BuildOption == CabinetBuildOption.BuildAndMove, mediaSymbol.SourceLineNumbers); | ||
| 232 | this.fileTransfers.Add(transfer); | ||
| 233 | } | ||
| 234 | |||
| 235 | return cabinetWorkItem; | ||
| 236 | } | ||
| 237 | |||
| 238 | //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades) | ||
| 239 | //{ | ||
| 240 | // ResolvedCabinet resolved = null; | ||
| 241 | |||
| 242 | // List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); | ||
| 243 | |||
| 244 | // foreach (var extension in this.BackendExtensions) | ||
| 245 | // { | ||
| 246 | // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); | ||
| 247 | // if (null != resolved) | ||
| 248 | // { | ||
| 249 | // break; | ||
| 250 | // } | ||
| 251 | // } | ||
| 252 | |||
| 253 | // return resolved; | ||
| 254 | //} | ||
| 255 | |||
| 256 | /// <summary> | ||
| 257 | /// Delegate for Cabinet Split Callback | ||
| 258 | /// </summary> | ||
| 259 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] | ||
| 260 | internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken); | ||
| 261 | |||
| 262 | /// <summary> | ||
| 263 | /// Call back to Add File Transfer for new Cab and add new Cab to Media table | ||
| 264 | /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe | ||
| 265 | /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored | ||
| 266 | /// </summary> | ||
| 267 | /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param> | ||
| 268 | /// <param name="newCabinetName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param> | ||
| 269 | /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param> | ||
| 270 | internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) | ||
| 271 | { | ||
| 272 | throw new NotImplementedException(); | ||
| 273 | #if TODO_CAB_SPANNING | ||
| 274 | // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads | ||
| 275 | var mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); | ||
| 276 | try | ||
| 277 | { | ||
| 278 | if (!mutex.WaitOne(0, false)) // Check if you can get the lock | ||
| 279 | { | ||
| 280 | // Cound not get the Lock | ||
| 281 | this.Messaging.Write(VerboseMessages.CabinetsSplitInParallel()); | ||
| 282 | mutex.WaitOne(); // Wait on other thread | ||
| 283 | } | ||
| 284 | |||
| 285 | var firstCabinetName = firstCabName + ".cab"; | ||
| 286 | var transferAdded = false; // Used for Error Handling | ||
| 287 | |||
| 288 | // Create File Transfer for new Cabinet using transfer of Base Cabinet | ||
| 289 | foreach (var transfer in this.FileTransfers) | ||
| 290 | { | ||
| 291 | if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) | ||
| 292 | { | ||
| 293 | var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); | ||
| 294 | var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); | ||
| 295 | |||
| 296 | var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers); | ||
| 297 | this.trackedFiles.Add(trackSource); | ||
| 298 | |||
| 299 | var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers); | ||
| 300 | this.trackedFiles.Add(trackTarget); | ||
| 301 | |||
| 302 | var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers); | ||
| 303 | this.fileTransfers.Add(newTransfer); | ||
| 304 | |||
| 305 | transferAdded = true; | ||
| 306 | break; | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | // Check if File Transfer was added | ||
| 311 | if (!transferAdded) | ||
| 312 | { | ||
| 313 | throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); | ||
| 314 | } | ||
| 315 | |||
| 316 | // Add the new Cabinets to media table using LastSequence of Base Cabinet | ||
| 317 | var mediaTable = this.Output.Tables["Media"]; | ||
| 318 | var wixFileTable = this.Output.Tables["WixFile"]; | ||
| 319 | var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain | ||
| 320 | var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain | ||
| 321 | var lastSplitCabinetFound = false; // Used for Error Handling | ||
| 322 | |||
| 323 | var lastCabinetOfThisSequence = String.Empty; | ||
| 324 | // Get the Value of Last Cabinet Added in this split Sequence from Dictionary | ||
| 325 | if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) | ||
| 326 | { | ||
| 327 | // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence | ||
| 328 | lastCabinetOfThisSequence = firstCabinetName; | ||
| 329 | } | ||
| 330 | |||
| 331 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
| 332 | { | ||
| 333 | // Get details for the Last Cabinet Added in this Split Sequence | ||
| 334 | if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
| 335 | { | ||
| 336 | lastSequenceForLastSplitCabAdded = mediaRow.LastSequence; | ||
| 337 | diskIDForLastSplitCabAdded = mediaRow.DiskId; | ||
| 338 | lastSplitCabinetFound = true; | ||
| 339 | } | ||
| 340 | |||
| 341 | // Check for Name Collision for the new Cabinet added | ||
| 342 | if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
| 343 | { | ||
| 344 | // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row | ||
| 345 | throw new WixException(ErrorMessages.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); | ||
| 346 | } | ||
| 347 | } | ||
| 348 | |||
| 349 | // Check if the last Split Cabinet was found in the Media Table | ||
| 350 | if (!lastSplitCabinetFound) | ||
| 351 | { | ||
| 352 | throw new WixException(ErrorMessages.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence)); | ||
| 353 | } | ||
| 354 | |||
| 355 | // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort | ||
| 356 | // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with | ||
| 357 | // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction | ||
| 358 | MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null); | ||
| 359 | newMediaRow.Cabinet = newCabinetName; | ||
| 360 | newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion | ||
| 361 | newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded; | ||
| 362 | |||
| 363 | // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique | ||
| 364 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
| 365 | { | ||
| 366 | // Check if this row comes after inserted row and it is not the new cabinet inserted row | ||
| 367 | if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) | ||
| 368 | { | ||
| 369 | mediaRow.DiskId++; // Increment DiskID | ||
| 370 | } | ||
| 371 | } | ||
| 372 | |||
| 373 | // Now Increment DiskID for All files Rows so that they refer to the right Media Row | ||
| 374 | foreach (WixFileRow wixFileRow in wixFileTable.Rows) | ||
| 375 | { | ||
| 376 | // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet | ||
| 377 | // This check will work as we have only one large file in every splitting cabinet | ||
| 378 | // If we want to support splitting cabinet with more large files we need to update this code | ||
| 379 | if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase)) | ||
| 380 | { | ||
| 381 | wixFileRow.DiskId++; // Increment DiskID | ||
| 382 | } | ||
| 383 | } | ||
| 384 | |||
| 385 | // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback | ||
| 386 | this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName; | ||
| 387 | |||
| 388 | mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails | ||
| 389 | } | ||
| 390 | finally | ||
| 391 | { | ||
| 392 | // Releasing the Mutex here | ||
| 393 | mutex.ReleaseMutex(); | ||
| 394 | } | ||
| 395 | #endif | ||
| 396 | } | ||
| 397 | |||
| 398 | |||
| 399 | /// <summary> | ||
| 400 | /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides | ||
| 401 | /// </summary> | ||
| 402 | private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) | ||
| 403 | { | ||
| 404 | // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size | ||
| 405 | var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); | ||
| 406 | var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); | ||
| 407 | |||
| 408 | // Supply Compile MediaTemplate Attributes to Cabinet Builder | ||
| 409 | if (this.MediaTemplate != null) | ||
| 410 | { | ||
| 411 | // Get the Value for Max Cab Size for File Splitting | ||
| 412 | var maxCabSizeForLargeFileInMB = 0; | ||
| 413 | try | ||
| 414 | { | ||
| 415 | // Override authored mcslfs value if environment variable is authored. | ||
| 416 | maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : this.MediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting; | ||
| 417 | |||
| 418 | var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; | ||
| 419 | maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; | ||
| 420 | } | ||
| 421 | catch (FormatException) | ||
| 422 | { | ||
| 423 | throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); | ||
| 424 | } | ||
| 425 | catch (OverflowException) | ||
| 426 | { | ||
| 427 | throw new WixException(ErrorMessages.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting)); | ||
| 428 | } | ||
| 429 | |||
| 430 | var maxPreCompressedSizeInMB = 0; | ||
| 431 | try | ||
| 432 | { | ||
| 433 | // Override authored mums value if environment variable is authored. | ||
| 434 | maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : this.MediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; | ||
| 435 | |||
| 436 | var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; | ||
| 437 | maxUncompressedMediaSize = maxPreCompressedSizeInMB; | ||
| 438 | } | ||
| 439 | catch (FormatException) | ||
| 440 | { | ||
| 441 | throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); | ||
| 442 | } | ||
| 443 | catch (OverflowException) | ||
| 444 | { | ||
| 445 | throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); | ||
| 446 | } | ||
| 447 | } | ||
| 448 | else | ||
| 449 | { | ||
| 450 | maxCabSizeForLargeFileSplitting = 0; | ||
| 451 | maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; | ||
| 452 | } | ||
| 453 | } | ||
| 454 | } | ||
| 455 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs new file mode 100644 index 00000000..47d8399f --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. | ||
| 15 | /// </summary> | ||
| 16 | internal class CreateDeltaPatchesCommand | ||
| 17 | { | ||
| 18 | public CreateDeltaPatchesCommand(List<IFileFacade> fileFacades, string intermediateFolder, WixPatchSymbol wixPatchId) | ||
| 19 | { | ||
| 20 | this.FileFacades = fileFacades; | ||
| 21 | this.IntermediateFolder = intermediateFolder; | ||
| 22 | this.WixPatchId = wixPatchId; | ||
| 23 | } | ||
| 24 | |||
| 25 | private IEnumerable<IFileFacade> FileFacades { get; } | ||
| 26 | |||
| 27 | private WixPatchSymbol WixPatchId { get; } | ||
| 28 | |||
| 29 | private string IntermediateFolder { get; } | ||
| 30 | |||
| 31 | public void Execute() | ||
| 32 | { | ||
| 33 | var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false; | ||
| 34 | var apiPatchingSymbolFlags = (PatchSymbolFlags)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0); | ||
| 35 | |||
| 36 | #if TODO_PATCHING_DELTA | ||
| 37 | foreach (FileFacade facade in this.FileFacades) | ||
| 38 | { | ||
| 39 | if (RowOperation.Modify == facade.File.Operation && | ||
| 40 | 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile)) | ||
| 41 | { | ||
| 42 | string deltaBase = String.Concat("delta_", facade.File.File); | ||
| 43 | string deltaFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".dpf")); | ||
| 44 | string headerFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".phd")); | ||
| 45 | |||
| 46 | bool retainRangeWarning = false; | ||
| 47 | |||
| 48 | if (PatchAPI.PatchInterop.CreateDelta( | ||
| 49 | deltaFile, | ||
| 50 | facade.WixFile.Source, | ||
| 51 | facade.DeltaPatchFile.Symbols, | ||
| 52 | facade.DeltaPatchFile.RetainOffsets, | ||
| 53 | new[] { facade.WixFile.PreviousSource }, | ||
| 54 | facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }), | ||
| 55 | facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }), | ||
| 56 | facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }), | ||
| 57 | facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }), | ||
| 58 | facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }), | ||
| 59 | apiPatchingSymbolFlags, | ||
| 60 | optimizePatchSizeForLargeFiles, | ||
| 61 | out retainRangeWarning)) | ||
| 62 | { | ||
| 63 | PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile); | ||
| 64 | |||
| 65 | facade.WixFile.Source = deltaFile; | ||
| 66 | facade.WixFile.DeltaPatchHeaderSource = headerFile; | ||
| 67 | } | ||
| 68 | |||
| 69 | if (retainRangeWarning) | ||
| 70 | { | ||
| 71 | // TODO: get patch family to add to warning message for PatchWiz parity. | ||
| 72 | Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File)); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | #endif | ||
| 77 | |||
| 78 | throw new NotImplementedException(); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs new file mode 100644 index 00000000..ff03413c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs | |||
| @@ -0,0 +1,250 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Globalization; | ||
| 7 | using System.IO; | ||
| 8 | using System.Text; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.WindowsInstaller; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class CreateIdtFileCommand | ||
| 14 | { | ||
| 15 | public CreateIdtFileCommand(IMessaging messaging, Table table, int codepage, string intermediateFolder, bool keepAddedColumns) | ||
| 16 | { | ||
| 17 | this.Messaging = messaging; | ||
| 18 | this.Table = table; | ||
| 19 | this.Codepage = codepage; | ||
| 20 | this.IntermediateFolder = intermediateFolder; | ||
| 21 | this.KeepAddedColumns = keepAddedColumns; | ||
| 22 | } | ||
| 23 | |||
| 24 | private IMessaging Messaging { get; } | ||
| 25 | |||
| 26 | private Table Table { get; } | ||
| 27 | |||
| 28 | private int Codepage { get; set; } | ||
| 29 | |||
| 30 | private string IntermediateFolder { get; } | ||
| 31 | |||
| 32 | private bool KeepAddedColumns { get; } | ||
| 33 | |||
| 34 | public string IdtPath { get; private set; } | ||
| 35 | |||
| 36 | public void Execute() | ||
| 37 | { | ||
| 38 | // write out the table to an IDT file | ||
| 39 | var encoding = GetCodepageEncoding(this.Codepage); | ||
| 40 | |||
| 41 | this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt")); | ||
| 42 | |||
| 43 | using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding)) | ||
| 44 | { | ||
| 45 | this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns) | ||
| 50 | { | ||
| 51 | if (table.Definition.Unreal) | ||
| 52 | { | ||
| 53 | return; | ||
| 54 | } | ||
| 55 | |||
| 56 | if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Length) | ||
| 57 | { | ||
| 58 | throw new WixException(ErrorMessages.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Length, TableDefinition.MaxColumnsInRealTable)); | ||
| 59 | } | ||
| 60 | |||
| 61 | // Tack on the table header, and flush before we start writing bytes directly to the stream. | ||
| 62 | var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns); | ||
| 63 | writer.Write(header); | ||
| 64 | writer.Flush(); | ||
| 65 | |||
| 66 | using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true)) | ||
| 67 | { | ||
| 68 | // Create an encoding that replaces characters with question marks, and doesn't throw. We'll | ||
| 69 | // use this in case of errors | ||
| 70 | Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage); | ||
| 71 | |||
| 72 | foreach (Row row in table.Rows) | ||
| 73 | { | ||
| 74 | if (row.Redundant) | ||
| 75 | { | ||
| 76 | continue; | ||
| 77 | } | ||
| 78 | |||
| 79 | string rowString = this.RowToIdtDefinition(row, keepAddedColumns); | ||
| 80 | byte[] rowBytes; | ||
| 81 | |||
| 82 | try | ||
| 83 | { | ||
| 84 | // GetBytes will throw an exception if any character doesn't match our current encoding | ||
| 85 | rowBytes = writer.Encoding.GetBytes(rowString); | ||
| 86 | } | ||
| 87 | catch (EncoderFallbackException) | ||
| 88 | { | ||
| 89 | this.Messaging.Write(ErrorMessages.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture))); | ||
| 90 | |||
| 91 | rowBytes = convertEncoding.GetBytes(rowString); | ||
| 92 | } | ||
| 93 | |||
| 94 | binary.Write(rowBytes, 0, rowBytes.Length); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns) | ||
| 100 | { | ||
| 101 | var first = true; | ||
| 102 | var columnString = new StringBuilder(); | ||
| 103 | var dataString = new StringBuilder(); | ||
| 104 | var tableString = new StringBuilder(); | ||
| 105 | |||
| 106 | tableString.Append(definition.Name); | ||
| 107 | foreach (var column in definition.Columns) | ||
| 108 | { | ||
| 109 | // Conditionally keep columns added in a transform; otherwise, | ||
| 110 | // break because columns can only be added at the end. | ||
| 111 | if (column.Added && !keepAddedColumns) | ||
| 112 | { | ||
| 113 | break; | ||
| 114 | } | ||
| 115 | |||
| 116 | if (column.Unreal) | ||
| 117 | { | ||
| 118 | continue; | ||
| 119 | } | ||
| 120 | |||
| 121 | if (!first) | ||
| 122 | { | ||
| 123 | columnString.Append('\t'); | ||
| 124 | dataString.Append('\t'); | ||
| 125 | } | ||
| 126 | |||
| 127 | columnString.Append(column.Name); | ||
| 128 | dataString.Append(ColumnIdtType(column)); | ||
| 129 | |||
| 130 | if (column.PrimaryKey) | ||
| 131 | { | ||
| 132 | tableString.AppendFormat("\t{0}", column.Name); | ||
| 133 | } | ||
| 134 | |||
| 135 | first = false; | ||
| 136 | } | ||
| 137 | columnString.Append("\r\n"); | ||
| 138 | columnString.Append(dataString); | ||
| 139 | columnString.Append("\r\n"); | ||
| 140 | columnString.Append(tableString); | ||
| 141 | columnString.Append("\r\n"); | ||
| 142 | |||
| 143 | return columnString.ToString(); | ||
| 144 | } | ||
| 145 | |||
| 146 | private string RowToIdtDefinition(Row row, bool keepAddedColumns) | ||
| 147 | { | ||
| 148 | var first = true; | ||
| 149 | var sb = new StringBuilder(); | ||
| 150 | |||
| 151 | foreach (var field in row.Fields) | ||
| 152 | { | ||
| 153 | // Conditionally keep columns added in a transform; otherwise, | ||
| 154 | // break because columns can only be added at the end. | ||
| 155 | if (field.Column.Added && !keepAddedColumns) | ||
| 156 | { | ||
| 157 | break; | ||
| 158 | } | ||
| 159 | |||
| 160 | if (field.Column.Unreal) | ||
| 161 | { | ||
| 162 | continue; | ||
| 163 | } | ||
| 164 | |||
| 165 | if (first) | ||
| 166 | { | ||
| 167 | first = false; | ||
| 168 | } | ||
| 169 | else | ||
| 170 | { | ||
| 171 | sb.Append('\t'); | ||
| 172 | } | ||
| 173 | |||
| 174 | sb.Append(this.FieldToIdtValue(field)); | ||
| 175 | } | ||
| 176 | sb.Append("\r\n"); | ||
| 177 | |||
| 178 | return sb.ToString(); | ||
| 179 | } | ||
| 180 | |||
| 181 | private string FieldToIdtValue(Field field) | ||
| 182 | { | ||
| 183 | var data = field.AsString(); | ||
| 184 | |||
| 185 | if (String.IsNullOrEmpty(data)) | ||
| 186 | { | ||
| 187 | return data; | ||
| 188 | } | ||
| 189 | |||
| 190 | // Special field value idt-specific escaping. | ||
| 191 | return data.Replace('\t', '\x10') | ||
| 192 | .Replace('\r', '\x11') | ||
| 193 | .Replace('\n', '\x19'); | ||
| 194 | } | ||
| 195 | |||
| 196 | private static Encoding GetCodepageEncoding(int codepage) | ||
| 197 | { | ||
| 198 | Encoding encoding; | ||
| 199 | |||
| 200 | // If UTF8 encoding, use the UTF8-specific constructor to avoid writing | ||
| 201 | // the byte order mark at the beginning of the file | ||
| 202 | if (codepage == Encoding.UTF8.CodePage) | ||
| 203 | { | ||
| 204 | encoding = new UTF8Encoding(false, true); | ||
| 205 | } | ||
| 206 | else | ||
| 207 | { | ||
| 208 | if (codepage == 0) | ||
| 209 | { | ||
| 210 | codepage = Encoding.ASCII.CodePage; | ||
| 211 | } | ||
| 212 | |||
| 213 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); | ||
| 214 | |||
| 215 | encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); | ||
| 216 | } | ||
| 217 | |||
| 218 | return encoding; | ||
| 219 | } | ||
| 220 | |||
| 221 | /// <summary> | ||
| 222 | /// Gets the type of the column in IDT format. | ||
| 223 | /// </summary> | ||
| 224 | /// <value>IDT format for column type.</value> | ||
| 225 | private static string ColumnIdtType(ColumnDefinition column) | ||
| 226 | { | ||
| 227 | char typeCharacter; | ||
| 228 | switch (column.Type) | ||
| 229 | { | ||
| 230 | case ColumnType.Number: | ||
| 231 | typeCharacter = column.Nullable ? 'I' : 'i'; | ||
| 232 | break; | ||
| 233 | case ColumnType.Preserved: | ||
| 234 | case ColumnType.String: | ||
| 235 | typeCharacter = column.Nullable ? 'S' : 's'; | ||
| 236 | break; | ||
| 237 | case ColumnType.Localized: | ||
| 238 | typeCharacter = column.Nullable ? 'L' : 'l'; | ||
| 239 | break; | ||
| 240 | case ColumnType.Object: | ||
| 241 | typeCharacter = column.Nullable ? 'V' : 'v'; | ||
| 242 | break; | ||
| 243 | default: | ||
| 244 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnknownColumnType, column.Type)); | ||
| 245 | } | ||
| 246 | |||
| 247 | return String.Concat(typeCharacter, column.Length); | ||
| 248 | } | ||
| 249 | } | ||
| 250 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs new file mode 100644 index 00000000..d0e25571 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs | |||
| @@ -0,0 +1,260 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Core.Native.Msi; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Data.WindowsInstaller; | ||
| 12 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | internal class CreateInstanceTransformsCommand | ||
| 16 | { | ||
| 17 | public CreateInstanceTransformsCommand(IntermediateSection section, WindowsInstallerData output, TableDefinitionCollection tableDefinitions, IBackendHelper backendHelper) | ||
| 18 | { | ||
| 19 | this.Section = section; | ||
| 20 | this.Output = output; | ||
| 21 | this.TableDefinitions = tableDefinitions; | ||
| 22 | this.BackendHelper = backendHelper; | ||
| 23 | } | ||
| 24 | |||
| 25 | private IntermediateSection Section { get; } | ||
| 26 | |||
| 27 | private WindowsInstallerData Output { get; } | ||
| 28 | |||
| 29 | public TableDefinitionCollection TableDefinitions { get; } | ||
| 30 | |||
| 31 | private IBackendHelper BackendHelper { get; } | ||
| 32 | |||
| 33 | public void Execute() | ||
| 34 | { | ||
| 35 | // Create and add substorages for instance transforms. | ||
| 36 | var wixInstanceTransformsSymbols = this.Section.Symbols.OfType<WixInstanceTransformsSymbol>(); | ||
| 37 | |||
| 38 | if (wixInstanceTransformsSymbols.Any()) | ||
| 39 | { | ||
| 40 | string targetProductCode = null; | ||
| 41 | string targetUpgradeCode = null; | ||
| 42 | string targetProductVersion = null; | ||
| 43 | |||
| 44 | var targetSummaryInformationTable = this.Output.Tables["_SummaryInformation"]; | ||
| 45 | var targetPropertyTable = this.Output.Tables["Property"]; | ||
| 46 | |||
| 47 | // Get the data from target database | ||
| 48 | foreach (var propertyRow in targetPropertyTable.Rows) | ||
| 49 | { | ||
| 50 | if ("ProductCode" == (string)propertyRow[0]) | ||
| 51 | { | ||
| 52 | targetProductCode = (string)propertyRow[1]; | ||
| 53 | } | ||
| 54 | else if ("ProductVersion" == (string)propertyRow[0]) | ||
| 55 | { | ||
| 56 | targetProductVersion = (string)propertyRow[1]; | ||
| 57 | } | ||
| 58 | else if ("UpgradeCode" == (string)propertyRow[0]) | ||
| 59 | { | ||
| 60 | targetUpgradeCode = (string)propertyRow[1]; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | // Index the Instance Component Rows, we'll get the Components rows from the real Component table. | ||
| 65 | var targetInstanceComponentTable = this.Section.Symbols.OfType<WixInstanceComponentSymbol>(); | ||
| 66 | var instanceComponentGuids = targetInstanceComponentTable.ToDictionary(t => t.Id.Id, t => (ComponentRow)null); | ||
| 67 | |||
| 68 | if (instanceComponentGuids.Any()) | ||
| 69 | { | ||
| 70 | var targetComponentTable = this.Output.Tables["Component"]; | ||
| 71 | foreach (ComponentRow componentRow in targetComponentTable.Rows) | ||
| 72 | { | ||
| 73 | var component = (string)componentRow[0]; | ||
| 74 | if (instanceComponentGuids.ContainsKey(component)) | ||
| 75 | { | ||
| 76 | instanceComponentGuids[component] = componentRow; | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | // Generate the instance transforms | ||
| 82 | foreach (var instanceSymbol in wixInstanceTransformsSymbols) | ||
| 83 | { | ||
| 84 | var instanceId = instanceSymbol.Id.Id; | ||
| 85 | |||
| 86 | var instanceTransform = new WindowsInstallerData(instanceSymbol.SourceLineNumbers); | ||
| 87 | instanceTransform.Type = OutputType.Transform; | ||
| 88 | instanceTransform.Codepage = this.Output.Codepage; | ||
| 89 | |||
| 90 | var instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 91 | string targetPlatformAndLanguage = null; | ||
| 92 | |||
| 93 | foreach (var summaryInformationRow in targetSummaryInformationTable.Rows) | ||
| 94 | { | ||
| 95 | if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE | ||
| 96 | { | ||
| 97 | targetPlatformAndLanguage = (string)summaryInformationRow[1]; | ||
| 98 | } | ||
| 99 | |||
| 100 | // Copy the row's data to the transform. | ||
| 101 | var copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(summaryInformationRow.SourceLineNumbers); | ||
| 102 | copyOfSummaryRow[0] = summaryInformationRow[0]; | ||
| 103 | copyOfSummaryRow[1] = summaryInformationRow[1]; | ||
| 104 | } | ||
| 105 | |||
| 106 | // Modify the appropriate properties. | ||
| 107 | var propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); | ||
| 108 | |||
| 109 | // Change the ProductCode property | ||
| 110 | var productCode = instanceSymbol.ProductCode; | ||
| 111 | if ("*" == productCode) | ||
| 112 | { | ||
| 113 | productCode = this.BackendHelper.CreateGuid(); | ||
| 114 | } | ||
| 115 | |||
| 116 | var productCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); | ||
| 117 | productCodeRow.Operation = RowOperation.Modify; | ||
| 118 | productCodeRow.Fields[1].Modified = true; | ||
| 119 | productCodeRow[0] = "ProductCode"; | ||
| 120 | productCodeRow[1] = productCode; | ||
| 121 | |||
| 122 | // Change the instance property | ||
| 123 | var instanceIdRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); | ||
| 124 | instanceIdRow.Operation = RowOperation.Modify; | ||
| 125 | instanceIdRow.Fields[1].Modified = true; | ||
| 126 | instanceIdRow[0] = instanceSymbol.PropertyId; | ||
| 127 | instanceIdRow[1] = instanceId; | ||
| 128 | |||
| 129 | if (!String.IsNullOrEmpty(instanceSymbol.ProductName)) | ||
| 130 | { | ||
| 131 | // Change the ProductName property | ||
| 132 | var productNameRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); | ||
| 133 | productNameRow.Operation = RowOperation.Modify; | ||
| 134 | productNameRow.Fields[1].Modified = true; | ||
| 135 | productNameRow[0] = "ProductName"; | ||
| 136 | productNameRow[1] = instanceSymbol.ProductName; | ||
| 137 | } | ||
| 138 | |||
| 139 | if (!String.IsNullOrEmpty(instanceSymbol.UpgradeCode)) | ||
| 140 | { | ||
| 141 | // Change the UpgradeCode property | ||
| 142 | var upgradeCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); | ||
| 143 | upgradeCodeRow.Operation = RowOperation.Modify; | ||
| 144 | upgradeCodeRow.Fields[1].Modified = true; | ||
| 145 | upgradeCodeRow[0] = "UpgradeCode"; | ||
| 146 | upgradeCodeRow[1] = instanceSymbol.UpgradeCode; | ||
| 147 | |||
| 148 | // Change the Upgrade table | ||
| 149 | var targetUpgradeTable = this.Output.Tables["Upgrade"]; | ||
| 150 | if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) | ||
| 151 | { | ||
| 152 | var upgradeId = instanceSymbol.UpgradeCode; | ||
| 153 | var upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); | ||
| 154 | foreach (var row in targetUpgradeTable.Rows) | ||
| 155 | { | ||
| 156 | // In case they are upgrading other codes to this new product, leave the ones that don't match the | ||
| 157 | // Product.UpgradeCode intact. | ||
| 158 | if (targetUpgradeCode == (string)row[0]) | ||
| 159 | { | ||
| 160 | var upgradeRow = upgradeTable.CreateRow(row.SourceLineNumbers); | ||
| 161 | upgradeRow.Operation = RowOperation.Add; | ||
| 162 | upgradeRow.Fields[0].Modified = true; | ||
| 163 | // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. | ||
| 164 | // upgradeRow.Fields[0].PreviousData = (string)row[0]; | ||
| 165 | |||
| 166 | // Inserting a new Upgrade record with the updated UpgradeCode | ||
| 167 | upgradeRow[0] = upgradeId; | ||
| 168 | upgradeRow[1] = row[1]; | ||
| 169 | upgradeRow[2] = row[2]; | ||
| 170 | upgradeRow[3] = row[3]; | ||
| 171 | upgradeRow[4] = row[4]; | ||
| 172 | upgradeRow[5] = row[5]; | ||
| 173 | upgradeRow[6] = row[6]; | ||
| 174 | |||
| 175 | // Delete the old row | ||
| 176 | var upgradeRemoveRow = upgradeTable.CreateRow(row.SourceLineNumbers); | ||
| 177 | upgradeRemoveRow.Operation = RowOperation.Delete; | ||
| 178 | upgradeRemoveRow[0] = row[0]; | ||
| 179 | upgradeRemoveRow[1] = row[1]; | ||
| 180 | upgradeRemoveRow[2] = row[2]; | ||
| 181 | upgradeRemoveRow[3] = row[3]; | ||
| 182 | upgradeRemoveRow[4] = row[4]; | ||
| 183 | upgradeRemoveRow[5] = row[5]; | ||
| 184 | upgradeRemoveRow[6] = row[6]; | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | // If there are instance Components generate new GUIDs for them. | ||
| 191 | if (0 < instanceComponentGuids.Count) | ||
| 192 | { | ||
| 193 | var componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); | ||
| 194 | foreach (var targetComponentRow in instanceComponentGuids.Values) | ||
| 195 | { | ||
| 196 | var guid = targetComponentRow.Guid; | ||
| 197 | if (!String.IsNullOrEmpty(guid)) | ||
| 198 | { | ||
| 199 | var instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); | ||
| 200 | instanceComponentRow.Operation = RowOperation.Modify; | ||
| 201 | instanceComponentRow.Fields[1].Modified = true; | ||
| 202 | instanceComponentRow[0] = targetComponentRow[0]; | ||
| 203 | instanceComponentRow[1] = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)); | ||
| 204 | instanceComponentRow[2] = targetComponentRow[2]; | ||
| 205 | instanceComponentRow[3] = targetComponentRow[3]; | ||
| 206 | instanceComponentRow[4] = targetComponentRow[4]; | ||
| 207 | instanceComponentRow[5] = targetComponentRow[5]; | ||
| 208 | } | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | // Update the summary information | ||
| 213 | var summaryRows = new Dictionary<int, Row>(instanceSummaryInformationTable.Rows.Count); | ||
| 214 | foreach (var row in instanceSummaryInformationTable.Rows) | ||
| 215 | { | ||
| 216 | summaryRows[(int)row[0]] = row; | ||
| 217 | |||
| 218 | if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
| 219 | { | ||
| 220 | row[1] = targetPlatformAndLanguage; | ||
| 221 | } | ||
| 222 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
| 223 | { | ||
| 224 | row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); | ||
| 225 | } | ||
| 226 | else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) | ||
| 227 | { | ||
| 228 | row[1] = 0; | ||
| 229 | } | ||
| 230 | else if ((int)SummaryInformation.Transform.Security == (int)row[0]) | ||
| 231 | { | ||
| 232 | row[1] = "4"; | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) | ||
| 237 | { | ||
| 238 | var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); | ||
| 239 | summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; | ||
| 240 | summaryRow[1] = targetPlatformAndLanguage; | ||
| 241 | } | ||
| 242 | else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) | ||
| 243 | { | ||
| 244 | var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); | ||
| 245 | summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; | ||
| 246 | summaryRow[1] = "0"; | ||
| 247 | } | ||
| 248 | else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) | ||
| 249 | { | ||
| 250 | var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); | ||
| 251 | summaryRow[0] = (int)SummaryInformation.Transform.Security; | ||
| 252 | summaryRow[1] = "4"; | ||
| 253 | } | ||
| 254 | |||
| 255 | this.Output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | } | ||
| 259 | } | ||
| 260 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs new file mode 100644 index 00000000..5c993f63 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Core.Native.Msi; | ||
| 10 | using WixToolset.Core.WindowsInstaller.Unbind; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Data.WindowsInstaller; | ||
| 14 | using WixToolset.Extensibility.Services; | ||
| 15 | |||
| 16 | internal class CreatePatchTransformsCommand | ||
| 17 | { | ||
| 18 | public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, string intermediateFolder) | ||
| 19 | { | ||
| 20 | this.Messaging = messaging; | ||
| 21 | this.BackendHelper = backendHelper; | ||
| 22 | this.Intermediate = intermediate; | ||
| 23 | this.IntermediateFolder = intermediateFolder; | ||
| 24 | } | ||
| 25 | |||
| 26 | private IMessaging Messaging { get; } | ||
| 27 | |||
| 28 | private IBackendHelper BackendHelper { get; } | ||
| 29 | |||
| 30 | private Intermediate Intermediate { get; } | ||
| 31 | |||
| 32 | private string IntermediateFolder { get; } | ||
| 33 | |||
| 34 | public IEnumerable<PatchTransform> PatchTransforms { get; private set; } | ||
| 35 | |||
| 36 | public IEnumerable<PatchTransform> Execute() | ||
| 37 | { | ||
| 38 | var patchTransforms = new List<PatchTransform>(); | ||
| 39 | |||
| 40 | var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).OfType<WixPatchBaselineSymbol>(); | ||
| 41 | |||
| 42 | foreach (var symbol in symbols) | ||
| 43 | { | ||
| 44 | WindowsInstallerData transform; | ||
| 45 | |||
| 46 | if (symbol.TransformFile is null) | ||
| 47 | { | ||
| 48 | var baselineData = this.GetData(symbol.BaselineFile.Path); | ||
| 49 | var updateData = this.GetData(symbol.UpdateFile.Path); | ||
| 50 | |||
| 51 | var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false); | ||
| 52 | transform = command.Execute(); | ||
| 53 | } | ||
| 54 | else | ||
| 55 | { | ||
| 56 | var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path. | ||
| 57 | |||
| 58 | var command = new UnbindTransformCommand(this.Messaging, this.BackendHelper, symbol.TransformFile.Path, exportBasePath, this.IntermediateFolder); | ||
| 59 | transform = command.Execute(); | ||
| 60 | } | ||
| 61 | |||
| 62 | patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform)); | ||
| 63 | } | ||
| 64 | |||
| 65 | this.PatchTransforms = patchTransforms; | ||
| 66 | |||
| 67 | return this.PatchTransforms; | ||
| 68 | } | ||
| 69 | |||
| 70 | private WindowsInstallerData GetData(string path) | ||
| 71 | { | ||
| 72 | var ext = Path.GetExtension(path); | ||
| 73 | |||
| 74 | if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase)) | ||
| 75 | { | ||
| 76 | using (var database = new Database(path, OpenDatabase.ReadOnly)) | ||
| 77 | { | ||
| 78 | var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path. | ||
| 79 | |||
| 80 | var isAdminImage = false; // TODO: need a better way to set this | ||
| 81 | |||
| 82 | var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true); | ||
| 83 | return command.Execute(); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | else // assume .wixpdb (or .wixout) | ||
| 87 | { | ||
| 88 | var data = WindowsInstallerData.Load(path, true); | ||
| 89 | return data; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs new file mode 100644 index 00000000..ba7c03a0 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs | |||
| @@ -0,0 +1,83 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | |||
| 11 | internal class CreateSpecialPropertiesCommand | ||
| 12 | { | ||
| 13 | public CreateSpecialPropertiesCommand(IntermediateSection section) | ||
| 14 | { | ||
| 15 | this.Section = section; | ||
| 16 | } | ||
| 17 | |||
| 18 | private IntermediateSection Section { get; } | ||
| 19 | |||
| 20 | public void Execute() | ||
| 21 | { | ||
| 22 | // Create lists of the properties that contribute to the special lists of properties. | ||
| 23 | var adminProperties = new SortedSet<string>(); | ||
| 24 | var secureProperties = new SortedSet<string>(); | ||
| 25 | var hiddenProperties = new SortedSet<string>(); | ||
| 26 | |||
| 27 | foreach (var wixPropertyRow in this.Section.Symbols.OfType<WixPropertySymbol>()) | ||
| 28 | { | ||
| 29 | if (wixPropertyRow.Admin) | ||
| 30 | { | ||
| 31 | adminProperties.Add(wixPropertyRow.PropertyRef); | ||
| 32 | } | ||
| 33 | |||
| 34 | if (wixPropertyRow.Hidden) | ||
| 35 | { | ||
| 36 | hiddenProperties.Add(wixPropertyRow.PropertyRef); | ||
| 37 | } | ||
| 38 | |||
| 39 | if (wixPropertyRow.Secure) | ||
| 40 | { | ||
| 41 | secureProperties.Add(wixPropertyRow.PropertyRef); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | // Hide properties for in-script custom actions that have HideTarget set. | ||
| 46 | var hideTargetCustomActions = this.Section.Symbols.OfType<CustomActionSymbol>().Where( | ||
| 47 | ca => ca.Hidden | ||
| 48 | && (ca.ExecutionType == CustomActionExecutionType.Deferred | ||
| 49 | || ca.ExecutionType == CustomActionExecutionType.Commit | ||
| 50 | || ca.ExecutionType == CustomActionExecutionType.Rollback)) | ||
| 51 | .Select(ca => ca.Id.Id); | ||
| 52 | hiddenProperties.UnionWith(hideTargetCustomActions); | ||
| 53 | |||
| 54 | // Ensure upgrade action properties are secure. | ||
| 55 | var actionProperties = this.Section.Symbols.OfType<UpgradeSymbol>().Select(u => u.ActionProperty); | ||
| 56 | secureProperties.UnionWith(actionProperties); | ||
| 57 | |||
| 58 | if (0 < adminProperties.Count) | ||
| 59 | { | ||
| 60 | this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "AdminProperties")) | ||
| 61 | { | ||
| 62 | Value = String.Join(";", adminProperties), | ||
| 63 | }); | ||
| 64 | } | ||
| 65 | |||
| 66 | if (0 < secureProperties.Count) | ||
| 67 | { | ||
| 68 | this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "SecureCustomProperties")) | ||
| 69 | { | ||
| 70 | Value = String.Join(";", secureProperties), | ||
| 71 | }); | ||
| 72 | } | ||
| 73 | |||
| 74 | if (0 < hiddenProperties.Count) | ||
| 75 | { | ||
| 76 | this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "MsiHiddenProperties")) | ||
| 77 | { | ||
| 78 | Value = String.Join(";", hiddenProperties) | ||
| 79 | }); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs new file mode 100644 index 00000000..d34ca3fe --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs | |||
| @@ -0,0 +1,1621 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Security.Cryptography; | ||
| 11 | using System.Text; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Data.WindowsInstaller; | ||
| 15 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 16 | using WixToolset.Extensibility; | ||
| 17 | using WixToolset.Extensibility.Services; | ||
| 18 | |||
| 19 | internal class CreateWindowsInstallerDataFromIRCommand | ||
| 20 | { | ||
| 21 | private static readonly char[] PathSeparatorChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; | ||
| 22 | |||
| 23 | public CreateWindowsInstallerDataFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, int codepage, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions, IWindowsInstallerBackendHelper backendHelper) | ||
| 24 | { | ||
| 25 | this.Messaging = messaging; | ||
| 26 | this.Section = section; | ||
| 27 | this.TableDefinitions = tableDefinitions; | ||
| 28 | this.Codepage = codepage; | ||
| 29 | this.BackendExtensions = backendExtensions; | ||
| 30 | this.BackendHelper = backendHelper; | ||
| 31 | this.GeneratedShortNames = new Dictionary<string, List<FileSymbol>>(); | ||
| 32 | } | ||
| 33 | |||
| 34 | private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; } | ||
| 35 | |||
| 36 | private IWindowsInstallerBackendHelper BackendHelper { get; } | ||
| 37 | |||
| 38 | private IMessaging Messaging { get; } | ||
| 39 | |||
| 40 | private TableDefinitionCollection TableDefinitions { get; } | ||
| 41 | |||
| 42 | private int Codepage { get; } | ||
| 43 | |||
| 44 | private IntermediateSection Section { get; } | ||
| 45 | |||
| 46 | private Dictionary<string, List<FileSymbol>> GeneratedShortNames { get; } | ||
| 47 | |||
| 48 | public WindowsInstallerData Data { get; private set; } | ||
| 49 | |||
| 50 | public WindowsInstallerData Execute() | ||
| 51 | { | ||
| 52 | this.Data = new WindowsInstallerData(this.Section.Symbols.First().SourceLineNumbers) | ||
| 53 | { | ||
| 54 | Codepage = this.Codepage, | ||
| 55 | Type = SectionTypeToOutputType(this.Section.Type) | ||
| 56 | }; | ||
| 57 | |||
| 58 | this.AddSectionToData(); | ||
| 59 | |||
| 60 | return this.Data; | ||
| 61 | } | ||
| 62 | |||
| 63 | private void AddSectionToData() | ||
| 64 | { | ||
| 65 | var cellsByTableAndRowId = new Dictionary<string, List<WixCustomTableCellSymbol>>(); | ||
| 66 | |||
| 67 | foreach (var symbol in this.Section.Symbols) | ||
| 68 | { | ||
| 69 | var unknownSymbol = false; | ||
| 70 | switch (symbol.Definition.Type) | ||
| 71 | { | ||
| 72 | case SymbolDefinitionType.AppSearch: | ||
| 73 | this.AddSymbolDefaultly(symbol); | ||
| 74 | this.Data.EnsureTable(this.TableDefinitions["Signature"]); | ||
| 75 | break; | ||
| 76 | |||
| 77 | case SymbolDefinitionType.Assembly: | ||
| 78 | this.AddAssemblySymbol((AssemblySymbol)symbol); | ||
| 79 | break; | ||
| 80 | |||
| 81 | case SymbolDefinitionType.BBControl: | ||
| 82 | this.AddBBControlSymbol((BBControlSymbol)symbol); | ||
| 83 | break; | ||
| 84 | |||
| 85 | case SymbolDefinitionType.Class: | ||
| 86 | this.AddClassSymbol((ClassSymbol)symbol); | ||
| 87 | break; | ||
| 88 | |||
| 89 | case SymbolDefinitionType.Control: | ||
| 90 | this.AddControlSymbol((ControlSymbol)symbol); | ||
| 91 | break; | ||
| 92 | |||
| 93 | case SymbolDefinitionType.ControlEvent: | ||
| 94 | this.AddControlEventSymbol((ControlEventSymbol)symbol); | ||
| 95 | break; | ||
| 96 | |||
| 97 | case SymbolDefinitionType.Component: | ||
| 98 | this.AddComponentSymbol((ComponentSymbol)symbol); | ||
| 99 | break; | ||
| 100 | |||
| 101 | case SymbolDefinitionType.CustomAction: | ||
| 102 | this.AddCustomActionSymbol((CustomActionSymbol)symbol); | ||
| 103 | break; | ||
| 104 | |||
| 105 | case SymbolDefinitionType.Dialog: | ||
| 106 | this.AddDialogSymbol((DialogSymbol)symbol); | ||
| 107 | break; | ||
| 108 | |||
| 109 | case SymbolDefinitionType.Directory: | ||
| 110 | this.AddDirectorySymbol((DirectorySymbol)symbol); | ||
| 111 | break; | ||
| 112 | |||
| 113 | case SymbolDefinitionType.DuplicateFile: | ||
| 114 | this.AddDuplicateFileSymbol((DuplicateFileSymbol)symbol); | ||
| 115 | break; | ||
| 116 | |||
| 117 | case SymbolDefinitionType.Environment: | ||
| 118 | this.AddEnvironmentSymbol((EnvironmentSymbol)symbol); | ||
| 119 | break; | ||
| 120 | |||
| 121 | case SymbolDefinitionType.Error: | ||
| 122 | this.AddErrorSymbol((ErrorSymbol)symbol); | ||
| 123 | break; | ||
| 124 | |||
| 125 | case SymbolDefinitionType.Feature: | ||
| 126 | this.AddFeatureSymbol((FeatureSymbol)symbol); | ||
| 127 | break; | ||
| 128 | |||
| 129 | case SymbolDefinitionType.File: | ||
| 130 | this.AddFileSymbol((FileSymbol)symbol); | ||
| 131 | break; | ||
| 132 | |||
| 133 | case SymbolDefinitionType.IniFile: | ||
| 134 | this.AddIniFileSymbol((IniFileSymbol)symbol); | ||
| 135 | break; | ||
| 136 | |||
| 137 | case SymbolDefinitionType.IniLocator: | ||
| 138 | this.AddIniLocatorSymbol((IniLocatorSymbol)symbol); | ||
| 139 | break; | ||
| 140 | |||
| 141 | case SymbolDefinitionType.Media: | ||
| 142 | this.AddMediaSymbol((MediaSymbol)symbol); | ||
| 143 | break; | ||
| 144 | |||
| 145 | case SymbolDefinitionType.ModuleConfiguration: | ||
| 146 | this.AddModuleConfigurationSymbol((ModuleConfigurationSymbol)symbol); | ||
| 147 | this.EnsureModuleIgnoredTable(symbol, "ModuleConfiguration"); | ||
| 148 | break; | ||
| 149 | |||
| 150 | case SymbolDefinitionType.ModuleSubstitution: | ||
| 151 | this.EnsureModuleIgnoredTable(symbol, "ModuleSubstitution"); | ||
| 152 | break; | ||
| 153 | |||
| 154 | case SymbolDefinitionType.MsiEmbeddedUI: | ||
| 155 | this.AddMsiEmbeddedUISymbol((MsiEmbeddedUISymbol)symbol); | ||
| 156 | break; | ||
| 157 | |||
| 158 | case SymbolDefinitionType.MsiServiceConfig: | ||
| 159 | this.AddMsiServiceConfigSymbol((MsiServiceConfigSymbol)symbol); | ||
| 160 | break; | ||
| 161 | |||
| 162 | case SymbolDefinitionType.MsiServiceConfigFailureActions: | ||
| 163 | this.AddMsiServiceConfigFailureActionsSymbol((MsiServiceConfigFailureActionsSymbol)symbol); | ||
| 164 | break; | ||
| 165 | |||
| 166 | case SymbolDefinitionType.MoveFile: | ||
| 167 | this.AddMoveFileSymbol((MoveFileSymbol)symbol); | ||
| 168 | break; | ||
| 169 | |||
| 170 | case SymbolDefinitionType.ProgId: | ||
| 171 | this.AddSymbolDefaultly(symbol); | ||
| 172 | this.Data.EnsureTable(this.TableDefinitions["Extension"]); | ||
| 173 | break; | ||
| 174 | |||
| 175 | case SymbolDefinitionType.Property: | ||
| 176 | this.AddPropertySymbol((PropertySymbol)symbol); | ||
| 177 | break; | ||
| 178 | |||
| 179 | case SymbolDefinitionType.RemoveFile: | ||
| 180 | this.AddRemoveFileSymbol((RemoveFileSymbol)symbol); | ||
| 181 | break; | ||
| 182 | |||
| 183 | case SymbolDefinitionType.Registry: | ||
| 184 | this.AddRegistrySymbol((RegistrySymbol)symbol); | ||
| 185 | break; | ||
| 186 | |||
| 187 | case SymbolDefinitionType.RegLocator: | ||
| 188 | this.AddRegLocatorSymbol((RegLocatorSymbol)symbol); | ||
| 189 | break; | ||
| 190 | |||
| 191 | case SymbolDefinitionType.RemoveRegistry: | ||
| 192 | this.AddRemoveRegistrySymbol((RemoveRegistrySymbol)symbol); | ||
| 193 | break; | ||
| 194 | |||
| 195 | case SymbolDefinitionType.ServiceControl: | ||
| 196 | this.AddServiceControlSymbol((ServiceControlSymbol)symbol); | ||
| 197 | break; | ||
| 198 | |||
| 199 | case SymbolDefinitionType.ServiceInstall: | ||
| 200 | this.AddServiceInstallSymbol((ServiceInstallSymbol)symbol); | ||
| 201 | break; | ||
| 202 | |||
| 203 | case SymbolDefinitionType.Shortcut: | ||
| 204 | this.AddShortcutSymbol((ShortcutSymbol)symbol); | ||
| 205 | break; | ||
| 206 | |||
| 207 | case SymbolDefinitionType.TextStyle: | ||
| 208 | this.AddTextStyleSymbol((TextStyleSymbol)symbol); | ||
| 209 | break; | ||
| 210 | |||
| 211 | case SymbolDefinitionType.Upgrade: | ||
| 212 | this.AddUpgradeSymbol((UpgradeSymbol)symbol); | ||
| 213 | break; | ||
| 214 | |||
| 215 | case SymbolDefinitionType.WixAction: | ||
| 216 | this.AddWixActionSymbol((WixActionSymbol)symbol); | ||
| 217 | break; | ||
| 218 | |||
| 219 | case SymbolDefinitionType.WixCustomTableCell: | ||
| 220 | this.IndexCustomTableCellSymbol((WixCustomTableCellSymbol)symbol, cellsByTableAndRowId); | ||
| 221 | break; | ||
| 222 | |||
| 223 | case SymbolDefinitionType.WixEnsureTable: | ||
| 224 | this.AddWixEnsureTableSymbol((WixEnsureTableSymbol)symbol); | ||
| 225 | break; | ||
| 226 | |||
| 227 | case SymbolDefinitionType.WixPackage: | ||
| 228 | this.AddWixPackageSymbol((WixPackageSymbol)symbol); | ||
| 229 | break; | ||
| 230 | |||
| 231 | // Symbols used internally and are not added to the output. | ||
| 232 | case SymbolDefinitionType.WixBuildInfo: | ||
| 233 | case SymbolDefinitionType.WixBindUpdatedFiles: | ||
| 234 | case SymbolDefinitionType.WixComponentGroup: | ||
| 235 | case SymbolDefinitionType.WixComplexReference: | ||
| 236 | case SymbolDefinitionType.WixDeltaPatchFile: | ||
| 237 | case SymbolDefinitionType.WixDeltaPatchSymbolPaths: | ||
| 238 | case SymbolDefinitionType.WixFragment: | ||
| 239 | case SymbolDefinitionType.WixFeatureGroup: | ||
| 240 | case SymbolDefinitionType.WixInstanceComponent: | ||
| 241 | case SymbolDefinitionType.WixInstanceTransforms: | ||
| 242 | case SymbolDefinitionType.WixFeatureModules: | ||
| 243 | case SymbolDefinitionType.WixGroup: | ||
| 244 | case SymbolDefinitionType.WixMediaTemplate: | ||
| 245 | case SymbolDefinitionType.WixMerge: | ||
| 246 | case SymbolDefinitionType.WixOrdering: | ||
| 247 | case SymbolDefinitionType.WixPatchBaseline: | ||
| 248 | case SymbolDefinitionType.WixPatchFamilyGroup: | ||
| 249 | case SymbolDefinitionType.WixPatch: | ||
| 250 | case SymbolDefinitionType.WixPatchRef: | ||
| 251 | case SymbolDefinitionType.WixPatchTarget: | ||
| 252 | case SymbolDefinitionType.WixProperty: | ||
| 253 | case SymbolDefinitionType.WixProductTag: | ||
| 254 | case SymbolDefinitionType.WixSimpleReference: | ||
| 255 | case SymbolDefinitionType.WixSuppressAction: | ||
| 256 | case SymbolDefinitionType.WixSuppressModularization: | ||
| 257 | case SymbolDefinitionType.WixUI: | ||
| 258 | case SymbolDefinitionType.WixVariable: | ||
| 259 | break; | ||
| 260 | |||
| 261 | // Already processed by LoadTableDefinitions. | ||
| 262 | case SymbolDefinitionType.WixCustomTable: | ||
| 263 | case SymbolDefinitionType.WixCustomTableColumn: | ||
| 264 | break; | ||
| 265 | |||
| 266 | case SymbolDefinitionType.MustBeFromAnExtension: | ||
| 267 | unknownSymbol = !this.AddSymbolFromExtension(symbol); | ||
| 268 | break; | ||
| 269 | |||
| 270 | default: | ||
| 271 | unknownSymbol = !this.AddSymbolDefaultly(symbol); | ||
| 272 | break; | ||
| 273 | } | ||
| 274 | |||
| 275 | if (unknownSymbol) | ||
| 276 | { | ||
| 277 | this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | this.AddIndexedCellSymbols(cellsByTableAndRowId); | ||
| 282 | this.EnsureRequiredTables(); | ||
| 283 | this.ReportGeneratedShortFileNameConflicts(); | ||
| 284 | this.ReportIllegalTables(); | ||
| 285 | this.ReportMismatchedModularizations(); | ||
| 286 | this.ReportWindowsInstallerDataInconsistencies(); | ||
| 287 | } | ||
| 288 | |||
| 289 | private void AddAssemblySymbol(AssemblySymbol symbol) | ||
| 290 | { | ||
| 291 | var attributes = symbol.Type == AssemblyType.Win32Assembly ? 1 : (int?)null; | ||
| 292 | |||
| 293 | var row = this.CreateRow(symbol, "MsiAssembly"); | ||
| 294 | row[0] = symbol.ComponentRef; | ||
| 295 | row[1] = symbol.FeatureRef; | ||
| 296 | row[2] = symbol.ManifestFileRef; | ||
| 297 | row[3] = symbol.ApplicationFileRef; | ||
| 298 | row[4] = attributes; | ||
| 299 | } | ||
| 300 | |||
| 301 | private void AddBBControlSymbol(BBControlSymbol symbol) | ||
| 302 | { | ||
| 303 | var attributes = symbol.Attributes; | ||
| 304 | attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; | ||
| 305 | attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; | ||
| 306 | attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; | ||
| 307 | attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; | ||
| 308 | attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; | ||
| 309 | attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; | ||
| 310 | attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; | ||
| 311 | attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; | ||
| 312 | |||
| 313 | var row = this.CreateRow(symbol, "BBControl"); | ||
| 314 | row[0] = symbol.BillboardRef; | ||
| 315 | row[1] = symbol.BBControl; | ||
| 316 | row[2] = symbol.Type; | ||
| 317 | row[3] = symbol.X; | ||
| 318 | row[4] = symbol.Y; | ||
| 319 | row[5] = symbol.Width; | ||
| 320 | row[6] = symbol.Height; | ||
| 321 | row[7] = attributes; | ||
| 322 | row[8] = symbol.Text; | ||
| 323 | } | ||
| 324 | |||
| 325 | private void AddClassSymbol(ClassSymbol symbol) | ||
| 326 | { | ||
| 327 | var row = this.CreateRow(symbol, "Class"); | ||
| 328 | row[0] = symbol.CLSID; | ||
| 329 | row[1] = symbol.Context; | ||
| 330 | row[2] = symbol.ComponentRef; | ||
| 331 | row[3] = symbol.DefaultProgIdRef; | ||
| 332 | row[4] = symbol.Description; | ||
| 333 | row[5] = symbol.AppIdRef; | ||
| 334 | row[6] = symbol.FileTypeMask; | ||
| 335 | row[7] = symbol.IconRef; | ||
| 336 | row[8] = symbol.IconIndex; | ||
| 337 | row[9] = symbol.DefInprocHandler; | ||
| 338 | row[10] = symbol.Argument; | ||
| 339 | row[11] = symbol.FeatureRef; | ||
| 340 | row[12] = symbol.RelativePath ? (int?)1 : null; | ||
| 341 | } | ||
| 342 | |||
| 343 | private void AddControlSymbol(ControlSymbol symbol) | ||
| 344 | { | ||
| 345 | var text = symbol.Text; | ||
| 346 | var attributes = symbol.Attributes; | ||
| 347 | attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; | ||
| 348 | attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; | ||
| 349 | attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; | ||
| 350 | attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; | ||
| 351 | attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; | ||
| 352 | attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; | ||
| 353 | attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; | ||
| 354 | attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; | ||
| 355 | |||
| 356 | // If we're tracking disk space, and this is a non-FormatSize Text control, | ||
| 357 | // and the text attribute starts with '[' and ends with ']', add a space. | ||
| 358 | // It is not necessary for the whole string to be a property, just those | ||
| 359 | // two characters matter. | ||
| 360 | if (symbol.TrackDiskSpace && | ||
| 361 | "Text" == symbol.Type && | ||
| 362 | WindowsInstallerConstants.MsidbControlAttributesFormatSize != (attributes & WindowsInstallerConstants.MsidbControlAttributesFormatSize) && | ||
| 363 | null != text && text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) | ||
| 364 | { | ||
| 365 | text = String.Concat(text, " "); | ||
| 366 | } | ||
| 367 | |||
| 368 | var row = this.CreateRow(symbol, "Control"); | ||
| 369 | row[0] = symbol.DialogRef; | ||
| 370 | row[1] = symbol.Control; | ||
| 371 | row[2] = symbol.Type; | ||
| 372 | row[3] = symbol.X; | ||
| 373 | row[4] = symbol.Y; | ||
| 374 | row[5] = symbol.Width; | ||
| 375 | row[6] = symbol.Height; | ||
| 376 | row[7] = attributes; | ||
| 377 | row[8] = symbol.Property; | ||
| 378 | row[9] = text; | ||
| 379 | row[10] = symbol.NextControlRef; | ||
| 380 | row[11] = symbol.Help; | ||
| 381 | } | ||
| 382 | |||
| 383 | private void AddControlEventSymbol(ControlEventSymbol symbol) | ||
| 384 | { | ||
| 385 | var row = this.CreateRow(symbol, "ControlEvent"); | ||
| 386 | row[0] = symbol.DialogRef; | ||
| 387 | row[1] = symbol.ControlRef; | ||
| 388 | row[2] = symbol.Event; | ||
| 389 | row[3] = symbol.Argument; | ||
| 390 | row[4] = String.IsNullOrEmpty(symbol.Condition) ? "1" : symbol.Condition; | ||
| 391 | row[5] = symbol.Ordering; | ||
| 392 | } | ||
| 393 | |||
| 394 | private void AddComponentSymbol(ComponentSymbol symbol) | ||
| 395 | { | ||
| 396 | var attributes = ComponentLocation.Either == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesOptional : 0; | ||
| 397 | attributes |= ComponentLocation.SourceOnly == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesSourceOnly : 0; | ||
| 398 | attributes |= ComponentKeyPathType.Registry == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath : 0; | ||
| 399 | attributes |= ComponentKeyPathType.OdbcDataSource == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource : 0; | ||
| 400 | attributes |= symbol.DisableRegistryReflection ? WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection : 0; | ||
| 401 | attributes |= symbol.NeverOverwrite ? WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite : 0; | ||
| 402 | attributes |= symbol.Permanent ? WindowsInstallerConstants.MsidbComponentAttributesPermanent : 0; | ||
| 403 | attributes |= symbol.SharedDllRefCount ? WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount : 0; | ||
| 404 | attributes |= symbol.Shared ? WindowsInstallerConstants.MsidbComponentAttributesShared : 0; | ||
| 405 | attributes |= symbol.Transitive ? WindowsInstallerConstants.MsidbComponentAttributesTransitive : 0; | ||
| 406 | attributes |= symbol.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence : 0; | ||
| 407 | attributes |= symbol.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0; | ||
| 408 | |||
| 409 | var row = this.CreateRow(symbol, "Component"); | ||
| 410 | row[0] = symbol.Id.Id; | ||
| 411 | row[1] = symbol.ComponentId; | ||
| 412 | row[2] = symbol.DirectoryRef; | ||
| 413 | row[3] = attributes; | ||
| 414 | row[4] = symbol.Condition; | ||
| 415 | row[5] = symbol.KeyPath; | ||
| 416 | } | ||
| 417 | |||
| 418 | private void AddCustomActionSymbol(CustomActionSymbol symbol) | ||
| 419 | { | ||
| 420 | var type = symbol.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0; | ||
| 421 | type |= symbol.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0; | ||
| 422 | type |= symbol.Hidden ? WindowsInstallerConstants.MsidbCustomActionTypeHideTarget : 0; | ||
| 423 | type |= symbol.Async ? WindowsInstallerConstants.MsidbCustomActionTypeAsync : 0; | ||
| 424 | type |= CustomActionExecutionType.FirstSequence == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence : 0; | ||
| 425 | type |= CustomActionExecutionType.OncePerProcess == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess : 0; | ||
| 426 | type |= CustomActionExecutionType.ClientRepeat == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat : 0; | ||
| 427 | type |= CustomActionExecutionType.Deferred == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript : 0; | ||
| 428 | type |= CustomActionExecutionType.Rollback == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeRollback : 0; | ||
| 429 | type |= CustomActionExecutionType.Commit == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeCommit : 0; | ||
| 430 | type |= CustomActionSourceType.File == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeSourceFile : 0; | ||
| 431 | type |= CustomActionSourceType.Directory == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeDirectory : 0; | ||
| 432 | type |= CustomActionSourceType.Property == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeProperty : 0; | ||
| 433 | type |= CustomActionTargetType.Dll == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeDll : 0; | ||
| 434 | type |= CustomActionTargetType.Exe == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeExe : 0; | ||
| 435 | type |= CustomActionTargetType.TextData == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeTextData : 0; | ||
| 436 | type |= CustomActionTargetType.JScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeJScript : 0; | ||
| 437 | type |= CustomActionTargetType.VBScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeVBScript : 0; | ||
| 438 | |||
| 439 | if (WindowsInstallerConstants.MsidbCustomActionTypeInScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeInScript)) | ||
| 440 | { | ||
| 441 | type |= symbol.Impersonate ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate; | ||
| 442 | type |= symbol.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0; | ||
| 443 | } | ||
| 444 | |||
| 445 | var row = this.CreateRow(symbol, "CustomAction"); | ||
| 446 | row[0] = symbol.Id.Id; | ||
| 447 | row[1] = type; | ||
| 448 | row[2] = symbol.Source; | ||
| 449 | row[3] = symbol.Target; | ||
| 450 | row[4] = symbol.PatchUninstall ? (int?)WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall : null; | ||
| 451 | |||
| 452 | if (OutputType.Module == this.Data.Type) | ||
| 453 | { | ||
| 454 | this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); | ||
| 455 | this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); | ||
| 456 | this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); | ||
| 457 | this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); | ||
| 458 | this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); | ||
| 459 | } | ||
| 460 | } | ||
| 461 | |||
| 462 | private void AddDialogSymbol(DialogSymbol symbol) | ||
| 463 | { | ||
| 464 | var attributes = symbol.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0; | ||
| 465 | attributes |= symbol.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; | ||
| 466 | attributes |= symbol.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; | ||
| 467 | attributes |= symbol.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0; | ||
| 468 | attributes |= symbol.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; | ||
| 469 | attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; | ||
| 470 | attributes |= symbol.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; | ||
| 471 | attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; | ||
| 472 | attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; | ||
| 473 | attributes |= symbol.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; | ||
| 474 | attributes |= symbol.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; | ||
| 475 | |||
| 476 | var row = this.CreateRow(symbol, "Dialog"); | ||
| 477 | row[0] = symbol.Id.Id; | ||
| 478 | row[1] = symbol.HCentering; | ||
| 479 | row[2] = symbol.VCentering; | ||
| 480 | row[3] = symbol.Width; | ||
| 481 | row[4] = symbol.Height; | ||
| 482 | row[5] = attributes; | ||
| 483 | row[6] = symbol.Title; | ||
| 484 | row[7] = symbol.FirstControlRef; | ||
| 485 | row[8] = symbol.DefaultControlRef; | ||
| 486 | row[9] = symbol.CancelControlRef; | ||
| 487 | |||
| 488 | this.Data.EnsureTable(this.TableDefinitions["ListBox"]); | ||
| 489 | } | ||
| 490 | |||
| 491 | private void AddDirectorySymbol(DirectorySymbol symbol) | ||
| 492 | { | ||
| 493 | (var name, var parentDir) = this.AddDirectorySubdirectories(symbol); | ||
| 494 | |||
| 495 | var shortName = symbol.ShortName; | ||
| 496 | var sourceShortname = symbol.SourceShortName; | ||
| 497 | |||
| 498 | if (String.IsNullOrEmpty(shortName) && name != null && name != "." && name != "SourceDir" && !this.BackendHelper.IsValidShortFilename(name, false)) | ||
| 499 | { | ||
| 500 | shortName = this.CreateShortName(name, false, "Directory", symbol.ParentDirectoryRef); | ||
| 501 | } | ||
| 502 | |||
| 503 | if (String.IsNullOrEmpty(sourceShortname) && !String.IsNullOrEmpty(symbol.SourceName) && !this.BackendHelper.IsValidShortFilename(symbol.SourceName, false)) | ||
| 504 | { | ||
| 505 | sourceShortname = this.CreateShortName(symbol.SourceName, false, "Directory", symbol.ParentDirectoryRef); | ||
| 506 | } | ||
| 507 | |||
| 508 | var sourceName = CreateMsiFilename(sourceShortname, symbol.SourceName); | ||
| 509 | var targetName = CreateMsiFilename(shortName, name); | ||
| 510 | |||
| 511 | if (String.IsNullOrEmpty(targetName)) | ||
| 512 | { | ||
| 513 | targetName = "."; | ||
| 514 | } | ||
| 515 | |||
| 516 | var defaultDir = String.IsNullOrEmpty(sourceName) || sourceName == targetName ? targetName : targetName + ":" + sourceName; | ||
| 517 | |||
| 518 | var row = this.CreateRow(symbol, "Directory"); | ||
| 519 | row[0] = symbol.Id.Id; | ||
| 520 | row[1] = parentDir; | ||
| 521 | row[2] = defaultDir; | ||
| 522 | |||
| 523 | if (OutputType.Module == this.Data.Type) | ||
| 524 | { | ||
| 525 | var directoryId = symbol.Id.Id; | ||
| 526 | |||
| 527 | if (WindowsInstallerStandard.IsStandardDirectory(directoryId)) | ||
| 528 | { | ||
| 529 | // If the directory table contains references to standard windows folders | ||
| 530 | // mergemod.dll will add customactions to set the MSM directory to | ||
| 531 | // the same directory as the standard windows folder and will add references to | ||
| 532 | // custom action to all the standard sequence tables. A problem will occur | ||
| 533 | // if the MSI does not have these tables as mergemod.dll does not add these | ||
| 534 | // tables to the MSI if absent. This code adds the tables in case mergemod.dll | ||
| 535 | // needs them. | ||
| 536 | this.Data.EnsureTable(this.TableDefinitions["CustomAction"]); | ||
| 537 | this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); | ||
| 538 | this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); | ||
| 539 | this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); | ||
| 540 | this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); | ||
| 541 | this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); | ||
| 542 | } | ||
| 543 | else | ||
| 544 | { | ||
| 545 | foreach (var standardDirectory in WindowsInstallerStandard.StandardDirectories()) | ||
| 546 | { | ||
| 547 | if (directoryId.StartsWith(standardDirectory.Id.Id, StringComparison.Ordinal)) | ||
| 548 | { | ||
| 549 | this.Messaging.Write(WarningMessages.StandardDirectoryConflictInMergeModule(symbol.SourceLineNumbers, directoryId, standardDirectory.Id.Id)); | ||
| 550 | } | ||
| 551 | } | ||
| 552 | } | ||
| 553 | } | ||
| 554 | } | ||
| 555 | |||
| 556 | private void AddDuplicateFileSymbol(DuplicateFileSymbol symbol) | ||
| 557 | { | ||
| 558 | var name = symbol.DestinationName; | ||
| 559 | if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) | ||
| 560 | { | ||
| 561 | symbol.DestinationShortName = this.CreateShortName(name, true, "CopyFile", symbol.ComponentRef, symbol.FileRef); | ||
| 562 | } | ||
| 563 | |||
| 564 | var row = this.CreateRow(symbol, "DuplicateFile"); | ||
| 565 | row[0] = symbol.Id.Id; | ||
| 566 | row[1] = symbol.ComponentRef; | ||
| 567 | row[2] = symbol.FileRef; | ||
| 568 | row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); | ||
| 569 | row[4] = symbol.DestinationFolder; | ||
| 570 | } | ||
| 571 | |||
| 572 | private void AddEnvironmentSymbol(EnvironmentSymbol symbol) | ||
| 573 | { | ||
| 574 | var action = String.Empty; | ||
| 575 | var system = symbol.System ? "*" : String.Empty; | ||
| 576 | var uninstall = symbol.Permanent ? String.Empty : "-"; | ||
| 577 | var value = symbol.Value; | ||
| 578 | |||
| 579 | switch (symbol.Action) | ||
| 580 | { | ||
| 581 | case EnvironmentActionType.Create: | ||
| 582 | action = "+"; | ||
| 583 | break; | ||
| 584 | case EnvironmentActionType.Set: | ||
| 585 | action = "="; | ||
| 586 | break; | ||
| 587 | case EnvironmentActionType.Remove: | ||
| 588 | action = "!"; | ||
| 589 | break; | ||
| 590 | } | ||
| 591 | |||
| 592 | switch (symbol.Part) | ||
| 593 | { | ||
| 594 | case EnvironmentPartType.First: | ||
| 595 | value = String.Concat(value, symbol.Separator, "[~]"); | ||
| 596 | break; | ||
| 597 | case EnvironmentPartType.Last: | ||
| 598 | value = String.Concat("[~]", symbol.Separator, value); | ||
| 599 | break; | ||
| 600 | } | ||
| 601 | |||
| 602 | var row = this.CreateRow(symbol, "Environment"); | ||
| 603 | row[0] = symbol.Id.Id; | ||
| 604 | row[1] = String.Concat(action, uninstall, system, symbol.Name); | ||
| 605 | row[2] = value; | ||
| 606 | row[3] = symbol.ComponentRef; | ||
| 607 | } | ||
| 608 | |||
| 609 | private void AddErrorSymbol(ErrorSymbol symbol) | ||
| 610 | { | ||
| 611 | var row = this.CreateRow(symbol, "Error"); | ||
| 612 | row[0] = Convert.ToInt32(symbol.Id.Id); | ||
| 613 | row[1] = symbol.Message; | ||
| 614 | } | ||
| 615 | |||
| 616 | private void AddFeatureSymbol(FeatureSymbol symbol) | ||
| 617 | { | ||
| 618 | var attributes = symbol.DisallowAbsent ? WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent : 0; | ||
| 619 | attributes |= symbol.DisallowAdvertise ? WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise : 0; | ||
| 620 | attributes |= FeatureInstallDefault.FollowParent == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFollowParent : 0; | ||
| 621 | attributes |= FeatureInstallDefault.Source == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorSource : 0; | ||
| 622 | attributes |= FeatureTypicalDefault.Advertise == symbol.TypicalDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise : 0; | ||
| 623 | |||
| 624 | var row = this.CreateRow(symbol, "Feature"); | ||
| 625 | row[0] = symbol.Id.Id; | ||
| 626 | row[1] = symbol.ParentFeatureRef; | ||
| 627 | row[2] = symbol.Title; | ||
| 628 | row[3] = symbol.Description; | ||
| 629 | row[4] = symbol.Display; | ||
| 630 | row[5] = symbol.Level; | ||
| 631 | row[6] = symbol.DirectoryRef; | ||
| 632 | row[7] = attributes; | ||
| 633 | } | ||
| 634 | |||
| 635 | private void AddFileSymbol(FileSymbol symbol) | ||
| 636 | { | ||
| 637 | var name = symbol.Name; | ||
| 638 | if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) | ||
| 639 | { | ||
| 640 | symbol.ShortName = this.CreateShortName(name, true, "File", symbol.DirectoryRef); | ||
| 641 | |||
| 642 | if (!this.GeneratedShortNames.TryGetValue(symbol.ShortName, out var potentialConflicts)) | ||
| 643 | { | ||
| 644 | potentialConflicts = new List<FileSymbol>(); | ||
| 645 | this.GeneratedShortNames.Add(symbol.ShortName, potentialConflicts); | ||
| 646 | } | ||
| 647 | |||
| 648 | potentialConflicts.Add(symbol); | ||
| 649 | } | ||
| 650 | |||
| 651 | var row = (FileRow)this.CreateRow(symbol, "File"); | ||
| 652 | row.File = symbol.Id.Id; | ||
| 653 | row.Component = symbol.ComponentRef; | ||
| 654 | row.FileName = CreateMsiFilename(symbol.ShortName, name); | ||
| 655 | row.FileSize = symbol.FileSize; | ||
| 656 | row.Version = symbol.Version; | ||
| 657 | row.Language = symbol.Language; | ||
| 658 | row.DiskId = symbol.DiskId ?? 1; // TODO: is 1 the correct thing to default here | ||
| 659 | row.Sequence = symbol.Sequence; | ||
| 660 | row.Source = symbol.Source.Path; | ||
| 661 | |||
| 662 | var attributes = (symbol.Attributes & FileSymbolAttributes.Checksum) == FileSymbolAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0; | ||
| 663 | attributes |= (symbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0; | ||
| 664 | attributes |= (symbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0; | ||
| 665 | attributes |= (symbol.Attributes & FileSymbolAttributes.Hidden) == FileSymbolAttributes.Hidden ? WindowsInstallerConstants.MsidbFileAttributesHidden : 0; | ||
| 666 | attributes |= (symbol.Attributes & FileSymbolAttributes.ReadOnly) == FileSymbolAttributes.ReadOnly ? WindowsInstallerConstants.MsidbFileAttributesReadOnly : 0; | ||
| 667 | attributes |= (symbol.Attributes & FileSymbolAttributes.System) == FileSymbolAttributes.System ? WindowsInstallerConstants.MsidbFileAttributesSystem : 0; | ||
| 668 | attributes |= (symbol.Attributes & FileSymbolAttributes.Vital) == FileSymbolAttributes.Vital ? WindowsInstallerConstants.MsidbFileAttributesVital : 0; | ||
| 669 | row.Attributes = attributes; | ||
| 670 | |||
| 671 | if (symbol.FontTitle != null) | ||
| 672 | { | ||
| 673 | var fontRow = this.CreateRow(symbol, "Font"); | ||
| 674 | fontRow[0] = symbol.Id.Id; | ||
| 675 | fontRow[1] = symbol.FontTitle; | ||
| 676 | } | ||
| 677 | |||
| 678 | if (symbol.SelfRegCost.HasValue) | ||
| 679 | { | ||
| 680 | var selfRegRow = this.CreateRow(symbol, "SelfReg"); | ||
| 681 | selfRegRow[0] = symbol.Id.Id; | ||
| 682 | selfRegRow[1] = symbol.SelfRegCost.Value; | ||
| 683 | } | ||
| 684 | } | ||
| 685 | |||
| 686 | private void AddIniFileSymbol(IniFileSymbol symbol) | ||
| 687 | { | ||
| 688 | var tableName = (IniFileActionType.AddLine == symbol.Action || IniFileActionType.AddTag == symbol.Action || IniFileActionType.CreateLine == symbol.Action) ? "IniFile" : "RemoveIniFile"; | ||
| 689 | |||
| 690 | var name = symbol.FileName; | ||
| 691 | if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) | ||
| 692 | { | ||
| 693 | symbol.ShortFileName = this.CreateShortName(name, true, "IniFile", symbol.ComponentRef); | ||
| 694 | } | ||
| 695 | |||
| 696 | var row = this.CreateRow(symbol, tableName); | ||
| 697 | row[0] = symbol.Id.Id; | ||
| 698 | row[1] = CreateMsiFilename(symbol.ShortFileName, name); | ||
| 699 | row[2] = symbol.DirProperty; | ||
| 700 | row[3] = symbol.Section; | ||
| 701 | row[4] = symbol.Key; | ||
| 702 | row[5] = symbol.Value; | ||
| 703 | row[6] = symbol.Action; | ||
| 704 | row[7] = symbol.ComponentRef; | ||
| 705 | } | ||
| 706 | |||
| 707 | private void AddIniLocatorSymbol(IniLocatorSymbol symbol) | ||
| 708 | { | ||
| 709 | var name = symbol.FileName; | ||
| 710 | if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) | ||
| 711 | { | ||
| 712 | symbol.ShortFileName = this.CreateShortName(name, true, "IniFileSearch"); | ||
| 713 | } | ||
| 714 | |||
| 715 | var row = this.CreateRow(symbol, "IniLocator"); | ||
| 716 | row[0] = symbol.Id.Id; | ||
| 717 | row[1] = CreateMsiFilename(symbol.ShortFileName, name); | ||
| 718 | row[2] = symbol.Section; | ||
| 719 | row[3] = symbol.Key; | ||
| 720 | row[4] = symbol.Field; | ||
| 721 | row[5] = symbol.Type; | ||
| 722 | } | ||
| 723 | |||
| 724 | private void AddMediaSymbol(MediaSymbol symbol) | ||
| 725 | { | ||
| 726 | if (this.Section.Type != SectionType.Module) | ||
| 727 | { | ||
| 728 | var row = (MediaRow)this.CreateRow(symbol, "Media"); | ||
| 729 | row.DiskId = symbol.DiskId; | ||
| 730 | row.LastSequence = symbol.LastSequence ?? 0; | ||
| 731 | row.DiskPrompt = symbol.DiskPrompt; | ||
| 732 | row.Cabinet = symbol.Cabinet; | ||
| 733 | row.VolumeLabel = symbol.VolumeLabel; | ||
| 734 | row.Source = symbol.Source; | ||
| 735 | } | ||
| 736 | } | ||
| 737 | |||
| 738 | private void AddModuleConfigurationSymbol(ModuleConfigurationSymbol symbol) | ||
| 739 | { | ||
| 740 | var row = this.CreateRow(symbol, "ModuleConfiguration"); | ||
| 741 | row[0] = symbol.Id.Id; | ||
| 742 | row[1] = symbol.Format; | ||
| 743 | row[2] = symbol.Type; | ||
| 744 | row[3] = symbol.ContextData; | ||
| 745 | row[4] = symbol.DefaultValue; | ||
| 746 | row[5] = (symbol.KeyNoOrphan ? WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan : 0) | | ||
| 747 | (symbol.NonNullable ? WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable : 0); | ||
| 748 | row[6] = symbol.DisplayName; | ||
| 749 | row[7] = symbol.Description; | ||
| 750 | row[8] = symbol.HelpLocation; | ||
| 751 | row[9] = symbol.HelpKeyword; | ||
| 752 | } | ||
| 753 | |||
| 754 | private void AddMsiEmbeddedUISymbol(MsiEmbeddedUISymbol symbol) | ||
| 755 | { | ||
| 756 | var attributes = symbol.EntryPoint ? WindowsInstallerConstants.MsidbEmbeddedUI : 0; | ||
| 757 | attributes |= symbol.SupportsBasicUI ? WindowsInstallerConstants.MsidbEmbeddedHandlesBasic : 0; | ||
| 758 | |||
| 759 | var row = this.CreateRow(symbol, "MsiEmbeddedUI"); | ||
| 760 | row[0] = symbol.Id.Id; | ||
| 761 | row[1] = symbol.FileName; | ||
| 762 | row[2] = attributes; | ||
| 763 | row[3] = symbol.MessageFilter; | ||
| 764 | row[4] = symbol.Source; | ||
| 765 | } | ||
| 766 | |||
| 767 | private void AddMsiServiceConfigSymbol(MsiServiceConfigSymbol symbol) | ||
| 768 | { | ||
| 769 | var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; | ||
| 770 | events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; | ||
| 771 | events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; | ||
| 772 | |||
| 773 | var row = this.CreateRow(symbol, "MsiServiceConfigFailureActions"); | ||
| 774 | row[0] = symbol.Id.Id; | ||
| 775 | row[1] = symbol.Name; | ||
| 776 | row[2] = events; | ||
| 777 | row[3] = symbol.ConfigType; | ||
| 778 | row[4] = symbol.Argument; | ||
| 779 | row[5] = symbol.ComponentRef; | ||
| 780 | } | ||
| 781 | |||
| 782 | private void AddMsiServiceConfigFailureActionsSymbol(MsiServiceConfigFailureActionsSymbol symbol) | ||
| 783 | { | ||
| 784 | var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; | ||
| 785 | events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; | ||
| 786 | events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; | ||
| 787 | |||
| 788 | var row = this.CreateRow(symbol, "MsiServiceConfig"); | ||
| 789 | row[0] = symbol.Id.Id; | ||
| 790 | row[1] = symbol.Name; | ||
| 791 | row[2] = events; | ||
| 792 | row[3] = symbol.ResetPeriod.HasValue ? symbol.ResetPeriod : null; | ||
| 793 | row[4] = symbol.RebootMessage ?? "[~]"; | ||
| 794 | row[5] = symbol.Command ?? "[~]"; | ||
| 795 | row[6] = symbol.Actions; | ||
| 796 | row[7] = symbol.DelayActions; | ||
| 797 | row[8] = symbol.ComponentRef; | ||
| 798 | } | ||
| 799 | |||
| 800 | private void AddMoveFileSymbol(MoveFileSymbol symbol) | ||
| 801 | { | ||
| 802 | var name = symbol.DestinationName; | ||
| 803 | if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) | ||
| 804 | { | ||
| 805 | symbol.DestinationShortName = this.CreateShortName(name, true, "MoveFile", symbol.ComponentRef); | ||
| 806 | } | ||
| 807 | |||
| 808 | var row = this.CreateRow(symbol, "MoveFile"); | ||
| 809 | row[0] = symbol.Id.Id; | ||
| 810 | row[1] = symbol.ComponentRef; | ||
| 811 | row[2] = symbol.SourceName; | ||
| 812 | row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); | ||
| 813 | row[4] = symbol.SourceFolder; | ||
| 814 | row[5] = symbol.DestFolder; | ||
| 815 | row[6] = symbol.Delete ? WindowsInstallerConstants.MsidbMoveFileOptionsMove : 0; | ||
| 816 | } | ||
| 817 | |||
| 818 | private void AddPropertySymbol(PropertySymbol symbol) | ||
| 819 | { | ||
| 820 | if (String.IsNullOrEmpty(symbol.Value)) | ||
| 821 | { | ||
| 822 | return; | ||
| 823 | } | ||
| 824 | |||
| 825 | var row = (PropertyRow)this.CreateRow(symbol, "Property"); | ||
| 826 | row.Property = symbol.Id.Id; | ||
| 827 | row.Value = symbol.Value; | ||
| 828 | } | ||
| 829 | |||
| 830 | private void AddRemoveFileSymbol(RemoveFileSymbol symbol) | ||
| 831 | { | ||
| 832 | var name = symbol.FileName; | ||
| 833 | if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) | ||
| 834 | { | ||
| 835 | symbol.ShortFileName = this.CreateShortName(name, true, "RemoveFile", symbol.ComponentRef); | ||
| 836 | } | ||
| 837 | |||
| 838 | var installMode = symbol.OnInstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall : 0; | ||
| 839 | installMode |= symbol.OnUninstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove : 0; | ||
| 840 | |||
| 841 | var row = this.CreateRow(symbol, "RemoveFile"); | ||
| 842 | row[0] = symbol.Id.Id; | ||
| 843 | row[1] = symbol.ComponentRef; | ||
| 844 | row[2] = CreateMsiFilename(symbol.ShortFileName, symbol.FileName); | ||
| 845 | row[3] = symbol.DirPropertyRef; | ||
| 846 | row[4] = installMode; | ||
| 847 | } | ||
| 848 | |||
| 849 | private void AddRegistrySymbol(RegistrySymbol symbol) | ||
| 850 | { | ||
| 851 | var value = symbol.Value; | ||
| 852 | |||
| 853 | switch (symbol.ValueType) | ||
| 854 | { | ||
| 855 | case RegistryValueType.Binary: | ||
| 856 | value = String.Concat("#x", value); | ||
| 857 | break; | ||
| 858 | case RegistryValueType.Expandable: | ||
| 859 | value = String.Concat("#%", value); | ||
| 860 | break; | ||
| 861 | case RegistryValueType.Integer: | ||
| 862 | value = String.Concat("#", value); | ||
| 863 | break; | ||
| 864 | case RegistryValueType.MultiString: | ||
| 865 | switch (symbol.ValueAction) | ||
| 866 | { | ||
| 867 | case RegistryValueActionType.Append: | ||
| 868 | value = String.Concat("[~]", value); | ||
| 869 | break; | ||
| 870 | case RegistryValueActionType.Prepend: | ||
| 871 | value = String.Concat(value, "[~]"); | ||
| 872 | break; | ||
| 873 | case RegistryValueActionType.Write: | ||
| 874 | default: | ||
| 875 | if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) | ||
| 876 | { | ||
| 877 | value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); | ||
| 878 | } | ||
| 879 | break; | ||
| 880 | } | ||
| 881 | break; | ||
| 882 | case RegistryValueType.String: | ||
| 883 | // escape the leading '#' character for string registry keys | ||
| 884 | if (null != value && value.StartsWith("#", StringComparison.Ordinal)) | ||
| 885 | { | ||
| 886 | value = String.Concat("#", value); | ||
| 887 | } | ||
| 888 | break; | ||
| 889 | } | ||
| 890 | |||
| 891 | var row = this.CreateRow(symbol, "Registry"); | ||
| 892 | row[0] = symbol.Id.Id; | ||
| 893 | row[1] = symbol.Root; | ||
| 894 | row[2] = symbol.Key; | ||
| 895 | row[3] = symbol.Name; | ||
| 896 | row[4] = value; | ||
| 897 | row[5] = symbol.ComponentRef; | ||
| 898 | } | ||
| 899 | |||
| 900 | private void AddRegLocatorSymbol(RegLocatorSymbol symbol) | ||
| 901 | { | ||
| 902 | var type = (int)symbol.Type; | ||
| 903 | type |= symbol.Win64 ? WindowsInstallerConstants.MsidbLocatorType64bit : 0; | ||
| 904 | |||
| 905 | var row = this.CreateRow(symbol, "RegLocator"); | ||
| 906 | row[0] = symbol.Id.Id; | ||
| 907 | row[1] = symbol.Root; | ||
| 908 | row[2] = symbol.Key; | ||
| 909 | row[3] = symbol.Name; | ||
| 910 | row[4] = type; | ||
| 911 | } | ||
| 912 | |||
| 913 | private void AddRemoveRegistrySymbol(RemoveRegistrySymbol symbol) | ||
| 914 | { | ||
| 915 | if (symbol.Action == RemoveRegistryActionType.RemoveOnInstall) | ||
| 916 | { | ||
| 917 | var row = this.CreateRow(symbol, "RemoveRegistry"); | ||
| 918 | row[0] = symbol.Id.Id; | ||
| 919 | row[1] = symbol.Root; | ||
| 920 | row[2] = symbol.Key; | ||
| 921 | row[3] = symbol.Name; | ||
| 922 | row[4] = symbol.ComponentRef; | ||
| 923 | } | ||
| 924 | else // Registry table is used to remove registry keys on uninstall. | ||
| 925 | { | ||
| 926 | var row = this.CreateRow(symbol, "Registry"); | ||
| 927 | row[0] = symbol.Id.Id; | ||
| 928 | row[1] = symbol.Root; | ||
| 929 | row[2] = symbol.Key; | ||
| 930 | row[3] = symbol.Name; | ||
| 931 | row[5] = symbol.ComponentRef; | ||
| 932 | } | ||
| 933 | } | ||
| 934 | |||
| 935 | private void AddServiceControlSymbol(ServiceControlSymbol symbol) | ||
| 936 | { | ||
| 937 | var events = symbol.InstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventDelete : 0; | ||
| 938 | events |= symbol.UninstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete : 0; | ||
| 939 | events |= symbol.InstallStart ? WindowsInstallerConstants.MsidbServiceControlEventStart : 0; | ||
| 940 | events |= symbol.UninstallStart ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStart : 0; | ||
| 941 | events |= symbol.InstallStop ? WindowsInstallerConstants.MsidbServiceControlEventStop : 0; | ||
| 942 | events |= symbol.UninstallStop ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStop : 0; | ||
| 943 | |||
| 944 | var row = this.CreateRow(symbol, "ServiceControl"); | ||
| 945 | row[0] = symbol.Id.Id; | ||
| 946 | row[1] = symbol.Name; | ||
| 947 | row[2] = events; | ||
| 948 | row[3] = symbol.Arguments; | ||
| 949 | if (symbol.Wait.HasValue) | ||
| 950 | { | ||
| 951 | row[4] = symbol.Wait.Value ? 1 : 0; | ||
| 952 | } | ||
| 953 | row[5] = symbol.ComponentRef; | ||
| 954 | } | ||
| 955 | |||
| 956 | private void AddServiceInstallSymbol(ServiceInstallSymbol symbol) | ||
| 957 | { | ||
| 958 | var errorControl = (int)symbol.ErrorControl; | ||
| 959 | errorControl |= symbol.Vital ? WindowsInstallerConstants.MsidbServiceInstallErrorControlVital : 0; | ||
| 960 | |||
| 961 | var serviceType = (int)symbol.ServiceType; | ||
| 962 | serviceType |= symbol.Interactive ? WindowsInstallerConstants.MsidbServiceInstallInteractive : 0; | ||
| 963 | |||
| 964 | var row = this.CreateRow(symbol, "ServiceInstall"); | ||
| 965 | row[0] = symbol.Id.Id; | ||
| 966 | row[1] = symbol.Name; | ||
| 967 | row[2] = symbol.DisplayName; | ||
| 968 | row[3] = serviceType; | ||
| 969 | row[4] = (int)symbol.StartType; | ||
| 970 | row[5] = errorControl; | ||
| 971 | row[6] = symbol.LoadOrderGroup; | ||
| 972 | row[7] = symbol.Dependencies; | ||
| 973 | row[8] = symbol.StartName; | ||
| 974 | row[9] = symbol.Password; | ||
| 975 | row[10] = symbol.Arguments; | ||
| 976 | row[11] = symbol.ComponentRef; | ||
| 977 | row[12] = symbol.Description; | ||
| 978 | } | ||
| 979 | |||
| 980 | private void AddShortcutSymbol(ShortcutSymbol symbol) | ||
| 981 | { | ||
| 982 | var name = symbol.Name; | ||
| 983 | if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) | ||
| 984 | { | ||
| 985 | symbol.ShortName = this.CreateShortName(name, true, "Shortcut", symbol.ComponentRef, symbol.DirectoryRef); | ||
| 986 | } | ||
| 987 | |||
| 988 | var row = this.CreateRow(symbol, "Shortcut"); | ||
| 989 | row[0] = symbol.Id.Id; | ||
| 990 | row[1] = symbol.DirectoryRef; | ||
| 991 | row[2] = CreateMsiFilename(symbol.ShortName, name); | ||
| 992 | row[3] = symbol.ComponentRef; | ||
| 993 | row[4] = symbol.Target; | ||
| 994 | row[5] = symbol.Arguments; | ||
| 995 | row[6] = symbol.Description; | ||
| 996 | row[7] = symbol.Hotkey; | ||
| 997 | row[8] = symbol.IconRef; | ||
| 998 | row[9] = symbol.IconIndex; | ||
| 999 | row[10] = (int?)symbol.Show; | ||
| 1000 | row[11] = symbol.WorkingDirectory; | ||
| 1001 | row[12] = symbol.DisplayResourceDll; | ||
| 1002 | row[13] = symbol.DisplayResourceId; | ||
| 1003 | row[14] = symbol.DescriptionResourceDll; | ||
| 1004 | row[15] = symbol.DescriptionResourceId; | ||
| 1005 | } | ||
| 1006 | |||
| 1007 | private void AddTextStyleSymbol(TextStyleSymbol symbol) | ||
| 1008 | { | ||
| 1009 | var styleBits = symbol.Bold ? WindowsInstallerConstants.MsidbTextStyleStyleBitsBold : 0; | ||
| 1010 | styleBits |= symbol.Italic ? WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic : 0; | ||
| 1011 | styleBits |= symbol.Strike ? WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike : 0; | ||
| 1012 | styleBits |= symbol.Underline ? WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline : 0; | ||
| 1013 | |||
| 1014 | long? color = null; | ||
| 1015 | |||
| 1016 | if (symbol.Red.HasValue || symbol.Green.HasValue || symbol.Blue.HasValue) | ||
| 1017 | { | ||
| 1018 | color = symbol.Red ?? 0; | ||
| 1019 | color += (long)(symbol.Green ?? 0) * 256; | ||
| 1020 | color += (long)(symbol.Blue ?? 0) * 65536; | ||
| 1021 | } | ||
| 1022 | |||
| 1023 | var row = this.CreateRow(symbol, "TextStyle"); | ||
| 1024 | row[0] = symbol.Id.Id; | ||
| 1025 | row[1] = symbol.FaceName; | ||
| 1026 | row[2] = symbol.Size; | ||
| 1027 | row[3] = color; | ||
| 1028 | row[4] = styleBits == 0 ? null : (int?)styleBits; | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | private void AddUpgradeSymbol(UpgradeSymbol symbol) | ||
| 1032 | { | ||
| 1033 | var row = (UpgradeRow)this.CreateRow(symbol, "Upgrade"); | ||
| 1034 | row.UpgradeCode = symbol.UpgradeCode; | ||
| 1035 | row.VersionMin = symbol.VersionMin; | ||
| 1036 | row.VersionMax = symbol.VersionMax; | ||
| 1037 | row.Language = symbol.Language; | ||
| 1038 | row.Remove = symbol.Remove; | ||
| 1039 | row.ActionProperty = symbol.ActionProperty; | ||
| 1040 | |||
| 1041 | var attributes = symbol.MigrateFeatures ? WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures : 0; | ||
| 1042 | attributes |= symbol.OnlyDetect ? WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect : 0; | ||
| 1043 | attributes |= symbol.IgnoreRemoveFailures ? WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure : 0; | ||
| 1044 | attributes |= symbol.VersionMinInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive : 0; | ||
| 1045 | attributes |= symbol.VersionMaxInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive : 0; | ||
| 1046 | attributes |= symbol.ExcludeLanguages ? WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive : 0; | ||
| 1047 | row.Attributes = attributes; | ||
| 1048 | } | ||
| 1049 | |||
| 1050 | private void AddWixActionSymbol(WixActionSymbol symbol) | ||
| 1051 | { | ||
| 1052 | // Get the table definition for the action (and ensure the proper table exists for a module). | ||
| 1053 | string sequenceTableName = null; | ||
| 1054 | switch (symbol.SequenceTable) | ||
| 1055 | { | ||
| 1056 | case SequenceTable.AdminExecuteSequence: | ||
| 1057 | if (OutputType.Module == this.Data.Type) | ||
| 1058 | { | ||
| 1059 | this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); | ||
| 1060 | sequenceTableName = "ModuleAdminExecuteSequence"; | ||
| 1061 | } | ||
| 1062 | else | ||
| 1063 | { | ||
| 1064 | sequenceTableName = "AdminExecuteSequence"; | ||
| 1065 | } | ||
| 1066 | break; | ||
| 1067 | case SequenceTable.AdminUISequence: | ||
| 1068 | if (OutputType.Module == this.Data.Type) | ||
| 1069 | { | ||
| 1070 | this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); | ||
| 1071 | sequenceTableName = "ModuleAdminUISequence"; | ||
| 1072 | } | ||
| 1073 | else | ||
| 1074 | { | ||
| 1075 | sequenceTableName = "AdminUISequence"; | ||
| 1076 | } | ||
| 1077 | break; | ||
| 1078 | case SequenceTable.AdvertiseExecuteSequence: | ||
| 1079 | if (OutputType.Module == this.Data.Type) | ||
| 1080 | { | ||
| 1081 | this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); | ||
| 1082 | sequenceTableName = "ModuleAdvtExecuteSequence"; | ||
| 1083 | } | ||
| 1084 | else | ||
| 1085 | { | ||
| 1086 | sequenceTableName = "AdvtExecuteSequence"; | ||
| 1087 | } | ||
| 1088 | break; | ||
| 1089 | case SequenceTable.InstallExecuteSequence: | ||
| 1090 | if (OutputType.Module == this.Data.Type) | ||
| 1091 | { | ||
| 1092 | this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); | ||
| 1093 | sequenceTableName = "ModuleInstallExecuteSequence"; | ||
| 1094 | } | ||
| 1095 | else | ||
| 1096 | { | ||
| 1097 | sequenceTableName = "InstallExecuteSequence"; | ||
| 1098 | } | ||
| 1099 | break; | ||
| 1100 | case SequenceTable.InstallUISequence: | ||
| 1101 | if (OutputType.Module == this.Data.Type) | ||
| 1102 | { | ||
| 1103 | this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); | ||
| 1104 | sequenceTableName = "ModuleInstallUISequence"; | ||
| 1105 | } | ||
| 1106 | else | ||
| 1107 | { | ||
| 1108 | sequenceTableName = "InstallUISequence"; | ||
| 1109 | } | ||
| 1110 | break; | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | // create the action sequence row in the output | ||
| 1114 | var row = this.CreateRow(symbol, sequenceTableName); | ||
| 1115 | |||
| 1116 | if (SectionType.Module == this.Section.Type) | ||
| 1117 | { | ||
| 1118 | row[0] = symbol.Action; | ||
| 1119 | if (0 != symbol.Sequence) | ||
| 1120 | { | ||
| 1121 | row[1] = symbol.Sequence; | ||
| 1122 | } | ||
| 1123 | else | ||
| 1124 | { | ||
| 1125 | var after = (null == symbol.Before); | ||
| 1126 | row[2] = after ? symbol.After : symbol.Before; | ||
| 1127 | row[3] = after ? 1 : 0; | ||
| 1128 | } | ||
| 1129 | row[4] = symbol.Condition; | ||
| 1130 | } | ||
| 1131 | else | ||
| 1132 | { | ||
| 1133 | row[0] = symbol.Action; | ||
| 1134 | row[1] = symbol.Condition; | ||
| 1135 | row[2] = symbol.Sequence; | ||
| 1136 | } | ||
| 1137 | } | ||
| 1138 | |||
| 1139 | private void IndexCustomTableCellSymbol(WixCustomTableCellSymbol wixCustomTableCellSymbol, Dictionary<string, List<WixCustomTableCellSymbol>> cellsByTableAndRowId) | ||
| 1140 | { | ||
| 1141 | var tableAndRowId = wixCustomTableCellSymbol.TableRef + "/" + wixCustomTableCellSymbol.RowId; | ||
| 1142 | if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells)) | ||
| 1143 | { | ||
| 1144 | cells = new List<WixCustomTableCellSymbol>(); | ||
| 1145 | cellsByTableAndRowId.Add(tableAndRowId, cells); | ||
| 1146 | } | ||
| 1147 | |||
| 1148 | cells.Add(wixCustomTableCellSymbol); | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | private void AddIndexedCellSymbols(Dictionary<string, List<WixCustomTableCellSymbol>> cellsByTableAndRowId) | ||
| 1152 | { | ||
| 1153 | foreach (var rowOfCells in cellsByTableAndRowId.Values) | ||
| 1154 | { | ||
| 1155 | var firstCellSymbol = rowOfCells[0]; | ||
| 1156 | var customTableDefinition = this.TableDefinitions[firstCellSymbol.TableRef]; | ||
| 1157 | |||
| 1158 | if (customTableDefinition.Unreal) | ||
| 1159 | { | ||
| 1160 | continue; | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | var customRow = this.CreateRow(firstCellSymbol, customTableDefinition); | ||
| 1164 | var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name); | ||
| 1165 | |||
| 1166 | #if TODO // SectionId seems like a good thing to preserve. | ||
| 1167 | customRow.SectionId = symbol.SectionId; | ||
| 1168 | #endif | ||
| 1169 | foreach (var cell in rowOfCells) | ||
| 1170 | { | ||
| 1171 | var data = cell.Data; | ||
| 1172 | |||
| 1173 | if (customRowFieldsByColumnName.TryGetValue(cell.ColumnRef, out var rowField)) | ||
| 1174 | { | ||
| 1175 | if (!String.IsNullOrEmpty(data)) | ||
| 1176 | { | ||
| 1177 | if (rowField.Column.Type == ColumnType.Number) | ||
| 1178 | { | ||
| 1179 | try | ||
| 1180 | { | ||
| 1181 | rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture); | ||
| 1182 | } | ||
| 1183 | catch (FormatException) | ||
| 1184 | { | ||
| 1185 | this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); | ||
| 1186 | } | ||
| 1187 | catch (OverflowException) | ||
| 1188 | { | ||
| 1189 | this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); | ||
| 1190 | } | ||
| 1191 | } | ||
| 1192 | else if (rowField.Column.Category == ColumnCategory.Identifier) | ||
| 1193 | { | ||
| 1194 | if (this.BackendHelper.IsValidIdentifier(data) || this.BackendHelper.IsValidBinderVariable(data) || ColumnCategory.Formatted == rowField.Column.Category) | ||
| 1195 | { | ||
| 1196 | rowField.Data = data; | ||
| 1197 | } | ||
| 1198 | else | ||
| 1199 | { | ||
| 1200 | this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data)); | ||
| 1201 | } | ||
| 1202 | } | ||
| 1203 | else | ||
| 1204 | { | ||
| 1205 | rowField.Data = data; | ||
| 1206 | } | ||
| 1207 | } | ||
| 1208 | } | ||
| 1209 | else | ||
| 1210 | { | ||
| 1211 | this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef)); | ||
| 1212 | } | ||
| 1213 | } | ||
| 1214 | |||
| 1215 | for (var i = 0; i < customTableDefinition.Columns.Length; ++i) | ||
| 1216 | { | ||
| 1217 | if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) | ||
| 1218 | { | ||
| 1219 | this.Messaging.Write(ErrorMessages.NoDataForColumn(firstCellSymbol.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); | ||
| 1220 | } | ||
| 1221 | } | ||
| 1222 | } | ||
| 1223 | } | ||
| 1224 | |||
| 1225 | private void AddWixEnsureTableSymbol(WixEnsureTableSymbol symbol) | ||
| 1226 | { | ||
| 1227 | var tableDefinition = this.TableDefinitions[symbol.Table]; | ||
| 1228 | this.Data.EnsureTable(tableDefinition); | ||
| 1229 | } | ||
| 1230 | |||
| 1231 | private void AddWixPackageSymbol(WixPackageSymbol symbol) | ||
| 1232 | { | ||
| 1233 | // TODO: Remove the following from the compiler and do it here instead. | ||
| 1234 | //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); | ||
| 1235 | //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); | ||
| 1236 | //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); | ||
| 1237 | //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); | ||
| 1238 | //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); | ||
| 1239 | //if (null != upgradeCode) | ||
| 1240 | //{ | ||
| 1241 | // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); | ||
| 1242 | //} | ||
| 1243 | |||
| 1244 | //if (isPerMachine) | ||
| 1245 | //{ | ||
| 1246 | // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); | ||
| 1247 | //} | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | private bool AddSymbolFromExtension(IntermediateSymbol symbol) | ||
| 1251 | { | ||
| 1252 | foreach (var extension in this.BackendExtensions) | ||
| 1253 | { | ||
| 1254 | if (extension.TryProcessSymbol(this.Section, symbol, this.Data, this.TableDefinitions)) | ||
| 1255 | { | ||
| 1256 | return true; | ||
| 1257 | } | ||
| 1258 | } | ||
| 1259 | |||
| 1260 | return false; | ||
| 1261 | } | ||
| 1262 | |||
| 1263 | private bool AddSymbolDefaultly(IntermediateSymbol symbol) => | ||
| 1264 | this.BackendHelper.TryAddSymbolToMatchingTableDefinitions(this.Section, symbol, this.Data, this.TableDefinitions); | ||
| 1265 | |||
| 1266 | private void EnsureModuleIgnoredTable(IntermediateSymbol symbol, string ignoredTable) | ||
| 1267 | { | ||
| 1268 | var tableDefinition = this.TableDefinitions["ModuleIgnoreTable"]; | ||
| 1269 | var table = this.Data.EnsureTable(tableDefinition); | ||
| 1270 | if (!table.Rows.Any(r => r.FieldAsString(0) == ignoredTable)) | ||
| 1271 | { | ||
| 1272 | var row = this.CreateRow(symbol, tableDefinition); | ||
| 1273 | row[0] = ignoredTable; | ||
| 1274 | } | ||
| 1275 | } | ||
| 1276 | |||
| 1277 | private (string, string) AddDirectorySubdirectories(DirectorySymbol symbol) | ||
| 1278 | { | ||
| 1279 | var directory = symbol.Name.Trim(PathSeparatorChars); | ||
| 1280 | var parentDir = symbol.ParentDirectoryRef ?? (symbol.Id.Id == "TARGETDIR" ? null : "TARGETDIR"); | ||
| 1281 | |||
| 1282 | var start = 0; | ||
| 1283 | var end = directory.IndexOfAny(PathSeparatorChars); | ||
| 1284 | var path = String.Empty; | ||
| 1285 | |||
| 1286 | while (start <= end) | ||
| 1287 | { | ||
| 1288 | var subdirectoryName = directory.Substring(start, end - start); | ||
| 1289 | |||
| 1290 | if (!String.IsNullOrEmpty(subdirectoryName)) | ||
| 1291 | { | ||
| 1292 | path = Path.Combine(path, subdirectoryName); | ||
| 1293 | |||
| 1294 | var id = this.BackendHelper.GenerateIdentifier("d", symbol.ParentDirectoryRef, path); | ||
| 1295 | var shortnameSubdirectory = this.BackendHelper.IsValidShortFilename(subdirectoryName, false) ? null : this.CreateShortName(subdirectoryName, false, "Directory", symbol.ParentDirectoryRef); | ||
| 1296 | |||
| 1297 | var subdirectoryRow = this.CreateRow(symbol, "Directory"); | ||
| 1298 | subdirectoryRow[0] = id; | ||
| 1299 | subdirectoryRow[1] = parentDir; | ||
| 1300 | subdirectoryRow[2] = CreateMsiFilename(shortnameSubdirectory, subdirectoryName); | ||
| 1301 | |||
| 1302 | parentDir = id; | ||
| 1303 | } | ||
| 1304 | |||
| 1305 | start = end + 1; | ||
| 1306 | end = symbol.Name.IndexOfAny(PathSeparatorChars, start); | ||
| 1307 | } | ||
| 1308 | |||
| 1309 | var name = (start == 0) ? directory : directory.Substring(start); | ||
| 1310 | |||
| 1311 | return (name, parentDir); | ||
| 1312 | } | ||
| 1313 | |||
| 1314 | private void EnsureRequiredTables() | ||
| 1315 | { | ||
| 1316 | // check for missing table and add them or display an error as appropriate | ||
| 1317 | switch (this.Data.Type) | ||
| 1318 | { | ||
| 1319 | case OutputType.Module: | ||
| 1320 | this.Data.EnsureTable(this.TableDefinitions["Component"]); | ||
| 1321 | this.Data.EnsureTable(this.TableDefinitions["Directory"]); | ||
| 1322 | this.Data.EnsureTable(this.TableDefinitions["FeatureComponents"]); | ||
| 1323 | this.Data.EnsureTable(this.TableDefinitions["File"]); | ||
| 1324 | this.Data.EnsureTable(this.TableDefinitions["ModuleComponents"]); | ||
| 1325 | this.Data.EnsureTable(this.TableDefinitions["ModuleSignature"]); | ||
| 1326 | break; | ||
| 1327 | |||
| 1328 | case OutputType.PatchCreation: | ||
| 1329 | var imageFamiliesCount = this.Data.Tables["ImageFamilies"]?.Rows.Count ?? 0; | ||
| 1330 | var targetImagesCount = this.Data.Tables["TargetImages"]?.Rows.Count ?? 0; | ||
| 1331 | var upgradedImagesCount = this.Data.Tables["UpgradedImages"]?.Rows.Count ?? 0; | ||
| 1332 | |||
| 1333 | if (imageFamiliesCount < 1) | ||
| 1334 | { | ||
| 1335 | this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("ImageFamilies")); | ||
| 1336 | } | ||
| 1337 | |||
| 1338 | if (targetImagesCount < 1) | ||
| 1339 | { | ||
| 1340 | this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("TargetImages")); | ||
| 1341 | } | ||
| 1342 | |||
| 1343 | if (upgradedImagesCount < 1) | ||
| 1344 | { | ||
| 1345 | this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("UpgradedImages")); | ||
| 1346 | } | ||
| 1347 | |||
| 1348 | this.Data.EnsureTable(this.TableDefinitions["Properties"]); | ||
| 1349 | break; | ||
| 1350 | |||
| 1351 | case OutputType.Product: | ||
| 1352 | this.Data.EnsureTable(this.TableDefinitions["File"]); | ||
| 1353 | this.Data.EnsureTable(this.TableDefinitions["Media"]); | ||
| 1354 | break; | ||
| 1355 | } | ||
| 1356 | } | ||
| 1357 | |||
| 1358 | private void ReportGeneratedShortFileNameConflicts() | ||
| 1359 | { | ||
| 1360 | foreach (var conflicts in this.GeneratedShortNames.Values.Where(l => l.Count > 1)) | ||
| 1361 | { | ||
| 1362 | this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict(conflicts[0].SourceLineNumbers, conflicts[0].ShortName)); | ||
| 1363 | for (var i = 1; i < conflicts.Count; ++i) | ||
| 1364 | { | ||
| 1365 | this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict2(conflicts[i].SourceLineNumbers)); | ||
| 1366 | } | ||
| 1367 | } | ||
| 1368 | } | ||
| 1369 | |||
| 1370 | private void ReportIllegalTables() | ||
| 1371 | { | ||
| 1372 | foreach (var table in this.Data.Tables) | ||
| 1373 | { | ||
| 1374 | switch (this.Data.Type) | ||
| 1375 | { | ||
| 1376 | case OutputType.Module: | ||
| 1377 | if ("BBControl" == table.Name || | ||
| 1378 | "Billboard" == table.Name || | ||
| 1379 | "CCPSearch" == table.Name || | ||
| 1380 | "Feature" == table.Name || | ||
| 1381 | "LaunchCondition" == table.Name || | ||
| 1382 | "Media" == table.Name || | ||
| 1383 | "Patch" == table.Name || | ||
| 1384 | "Upgrade" == table.Name || | ||
| 1385 | "WixMerge" == table.Name) | ||
| 1386 | { | ||
| 1387 | foreach (Row row in table.Rows) | ||
| 1388 | { | ||
| 1389 | this.Messaging.Write(ErrorMessages.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name)); | ||
| 1390 | } | ||
| 1391 | } | ||
| 1392 | else if ("Error" == table.Name) | ||
| 1393 | { | ||
| 1394 | foreach (var row in table.Rows) | ||
| 1395 | { | ||
| 1396 | this.Messaging.Write(WarningMessages.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name)); | ||
| 1397 | } | ||
| 1398 | } | ||
| 1399 | break; | ||
| 1400 | |||
| 1401 | case OutputType.PatchCreation: | ||
| 1402 | if (!table.Definition.Unreal && | ||
| 1403 | "_SummaryInformation" != table.Name && | ||
| 1404 | "ExternalFiles" != table.Name && | ||
| 1405 | "FamilyFileRanges" != table.Name && | ||
| 1406 | "ImageFamilies" != table.Name && | ||
| 1407 | "PatchMetadata" != table.Name && | ||
| 1408 | "PatchSequence" != table.Name && | ||
| 1409 | "Properties" != table.Name && | ||
| 1410 | "TargetFiles_OptionalData" != table.Name && | ||
| 1411 | "TargetImages" != table.Name && | ||
| 1412 | "UpgradedFiles_OptionalData" != table.Name && | ||
| 1413 | "UpgradedFilesToIgnore" != table.Name && | ||
| 1414 | "UpgradedImages" != table.Name) | ||
| 1415 | { | ||
| 1416 | foreach (var row in table.Rows) | ||
| 1417 | { | ||
| 1418 | this.Messaging.Write(ErrorMessages.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name)); | ||
| 1419 | } | ||
| 1420 | } | ||
| 1421 | break; | ||
| 1422 | |||
| 1423 | case OutputType.Patch: | ||
| 1424 | if (!table.Definition.Unreal && | ||
| 1425 | "_SummaryInformation" != table.Name && | ||
| 1426 | "Media" != table.Name && | ||
| 1427 | "MsiFileHash" != table.Name && | ||
| 1428 | "MsiPatchMetadata" != table.Name && | ||
| 1429 | "MsiPatchSequence" != table.Name) | ||
| 1430 | { | ||
| 1431 | foreach (var row in table.Rows) | ||
| 1432 | { | ||
| 1433 | this.Messaging.Write(ErrorMessages.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name)); | ||
| 1434 | } | ||
| 1435 | } | ||
| 1436 | break; | ||
| 1437 | |||
| 1438 | case OutputType.Product: | ||
| 1439 | if ("ModuleAdminExecuteSequence" == table.Name || | ||
| 1440 | "ModuleAdminUISequence" == table.Name || | ||
| 1441 | "ModuleAdvtExecuteSequence" == table.Name || | ||
| 1442 | "ModuleAdvtUISequence" == table.Name || | ||
| 1443 | "ModuleComponents" == table.Name || | ||
| 1444 | "ModuleConfiguration" == table.Name || | ||
| 1445 | "ModuleDependency" == table.Name || | ||
| 1446 | "ModuleExclusion" == table.Name || | ||
| 1447 | "ModuleIgnoreTable" == table.Name || | ||
| 1448 | "ModuleInstallExecuteSequence" == table.Name || | ||
| 1449 | "ModuleInstallUISequence" == table.Name || | ||
| 1450 | "ModuleSignature" == table.Name || | ||
| 1451 | "ModuleSubstitution" == table.Name) | ||
| 1452 | { | ||
| 1453 | foreach (var row in table.Rows) | ||
| 1454 | { | ||
| 1455 | this.Messaging.Write(WarningMessages.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name)); | ||
| 1456 | } | ||
| 1457 | } | ||
| 1458 | break; | ||
| 1459 | } | ||
| 1460 | } | ||
| 1461 | } | ||
| 1462 | |||
| 1463 | private void ReportMismatchedModularizations() | ||
| 1464 | { | ||
| 1465 | // verify that modularization types match for foreign key relationships | ||
| 1466 | foreach (var tableDefinition in this.TableDefinitions) | ||
| 1467 | { | ||
| 1468 | foreach (var columnDefinition in tableDefinition.Columns) | ||
| 1469 | { | ||
| 1470 | if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.KeyColumn.HasValue) | ||
| 1471 | { | ||
| 1472 | if (this.TableDefinitions.TryGet(columnDefinition.KeyTable, out var keyTableDefinition)) | ||
| 1473 | { | ||
| 1474 | var keyColumnIndex = columnDefinition.KeyColumn ?? -1; | ||
| 1475 | |||
| 1476 | if (keyColumnIndex <= 0 || keyColumnIndex > keyTableDefinition.Columns.Length) | ||
| 1477 | { | ||
| 1478 | this.Messaging.Write(ErrorMessages.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex)); | ||
| 1479 | } | ||
| 1480 | else if (keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType) | ||
| 1481 | { | ||
| 1482 | this.Messaging.Write(WarningMessages.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType.ToString())); | ||
| 1483 | } | ||
| 1484 | } | ||
| 1485 | // else - ignore missing table definitions as that error is caught in other places | ||
| 1486 | } | ||
| 1487 | } | ||
| 1488 | } | ||
| 1489 | } | ||
| 1490 | |||
| 1491 | private void ReportWindowsInstallerDataInconsistencies() | ||
| 1492 | { | ||
| 1493 | // Get the output's minimum installer version | ||
| 1494 | var outputInstallerVersion = Int32.MaxValue; | ||
| 1495 | |||
| 1496 | if (this.Data.Tables.TryGetTable("_SummaryInformation", out var summaryInformationTable)) | ||
| 1497 | { | ||
| 1498 | outputInstallerVersion = summaryInformationTable.Rows.FirstOrDefault(r => 14 == r.FieldAsInteger(0))?.FieldAsInteger(1) ?? Int32.MaxValue; | ||
| 1499 | } | ||
| 1500 | |||
| 1501 | // Ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40). | ||
| 1502 | if (outputInstallerVersion <= 100 && OutputType.Product == this.Data.Type) | ||
| 1503 | { | ||
| 1504 | this.Data.EnsureTable(this.TableDefinitions["Error"]); | ||
| 1505 | } | ||
| 1506 | |||
| 1507 | // Check for the presence of tables/rows/columns that require MSI 1.1 or later. | ||
| 1508 | if (outputInstallerVersion < 110) | ||
| 1509 | { | ||
| 1510 | if (this.Data.Tables.TryGetTable("IsolatedComponent", out var isolatedComponentTable)) | ||
| 1511 | { | ||
| 1512 | foreach (var row in isolatedComponentTable.Rows) | ||
| 1513 | { | ||
| 1514 | this.Messaging.Write(WarningMessages.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion)); | ||
| 1515 | } | ||
| 1516 | } | ||
| 1517 | } | ||
| 1518 | |||
| 1519 | // Check for the presence of tables/rows/columns that require MSI 4.0 or later | ||
| 1520 | if (outputInstallerVersion < 400) | ||
| 1521 | { | ||
| 1522 | if (this.Data.Tables.TryGetTable("Shortcut", out var shortcutTable)) | ||
| 1523 | { | ||
| 1524 | foreach (var row in shortcutTable.Rows) | ||
| 1525 | { | ||
| 1526 | if (null != row[12] || null != row[13] || null != row[14] || null != row[15]) | ||
| 1527 | { | ||
| 1528 | this.Messaging.Write(WarningMessages.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion)); | ||
| 1529 | } | ||
| 1530 | } | ||
| 1531 | } | ||
| 1532 | } | ||
| 1533 | } | ||
| 1534 | |||
| 1535 | private static OutputType SectionTypeToOutputType(SectionType type) | ||
| 1536 | { | ||
| 1537 | switch (type) | ||
| 1538 | { | ||
| 1539 | case SectionType.Bundle: | ||
| 1540 | return OutputType.Bundle; | ||
| 1541 | case SectionType.Module: | ||
| 1542 | return OutputType.Module; | ||
| 1543 | case SectionType.Product: | ||
| 1544 | return OutputType.Product; | ||
| 1545 | case SectionType.PatchCreation: | ||
| 1546 | return OutputType.PatchCreation; | ||
| 1547 | case SectionType.Patch: | ||
| 1548 | return OutputType.Patch; | ||
| 1549 | |||
| 1550 | default: | ||
| 1551 | throw new ArgumentOutOfRangeException(nameof(type)); | ||
| 1552 | } | ||
| 1553 | } | ||
| 1554 | |||
| 1555 | private Row CreateRow(IntermediateSymbol symbol, string tableDefinitionName) => | ||
| 1556 | this.CreateRow(symbol, this.TableDefinitions[tableDefinitionName]); | ||
| 1557 | |||
| 1558 | private Row CreateRow(IntermediateSymbol symbol, TableDefinition tableDefinition) => | ||
| 1559 | this.BackendHelper.CreateRow(this.Section, symbol, this.Data, tableDefinition); | ||
| 1560 | |||
| 1561 | |||
| 1562 | private string CreateShortName(string longName, bool keepExtension, params string[] args) | ||
| 1563 | { | ||
| 1564 | longName = longName.ToLowerInvariant(); | ||
| 1565 | |||
| 1566 | // collect all the data | ||
| 1567 | var strings = new List<string>(1 + args.Length); | ||
| 1568 | strings.Add(longName); | ||
| 1569 | strings.AddRange(args); | ||
| 1570 | |||
| 1571 | // prepare for hashing | ||
| 1572 | var stringData = String.Join("|", strings); | ||
| 1573 | var data = Encoding.UTF8.GetBytes(stringData); | ||
| 1574 | |||
| 1575 | // hash the data | ||
| 1576 | byte[] hash; | ||
| 1577 | using (var sha1 = new SHA1CryptoServiceProvider()) | ||
| 1578 | { | ||
| 1579 | hash = sha1.ComputeHash(data); | ||
| 1580 | } | ||
| 1581 | |||
| 1582 | // generate the short file/directory name without an extension | ||
| 1583 | var shortName = new StringBuilder(Convert.ToBase64String(hash)); | ||
| 1584 | shortName.Length = 8; | ||
| 1585 | shortName.Replace('+', '-').Replace('/', '_'); | ||
| 1586 | |||
| 1587 | if (keepExtension) | ||
| 1588 | { | ||
| 1589 | var extension = Path.GetExtension(longName); | ||
| 1590 | |||
| 1591 | if (4 < extension.Length) | ||
| 1592 | { | ||
| 1593 | extension = extension.Substring(0, 4); | ||
| 1594 | } | ||
| 1595 | |||
| 1596 | shortName.Append(extension); | ||
| 1597 | |||
| 1598 | // check the generated short name to ensure its still legal (the extension may not be legal) | ||
| 1599 | if (!this.BackendHelper.IsValidShortFilename(shortName.ToString(), false)) | ||
| 1600 | { | ||
| 1601 | // remove the extension (by truncating the generated file name back to the generated characters) | ||
| 1602 | shortName.Length -= extension.Length; | ||
| 1603 | } | ||
| 1604 | } | ||
| 1605 | |||
| 1606 | return shortName.ToString().ToLowerInvariant(); | ||
| 1607 | } | ||
| 1608 | |||
| 1609 | private static string CreateMsiFilename(string shortName, string longName) | ||
| 1610 | { | ||
| 1611 | if (String.IsNullOrEmpty(shortName) || String.Equals(shortName, longName, StringComparison.OrdinalIgnoreCase)) | ||
| 1612 | { | ||
| 1613 | return longName; | ||
| 1614 | } | ||
| 1615 | else | ||
| 1616 | { | ||
| 1617 | return shortName + "|" + longName; | ||
| 1618 | } | ||
| 1619 | } | ||
| 1620 | } | ||
| 1621 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs new file mode 100644 index 00000000..7c1e085c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs | |||
| @@ -0,0 +1,221 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.ComponentModel; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using System.Runtime.InteropServices; | ||
| 12 | using WixToolset.Core.Native; | ||
| 13 | using WixToolset.Core.Native.Msi; | ||
| 14 | using WixToolset.Core.Native.Msm; | ||
| 15 | using WixToolset.Data; | ||
| 16 | using WixToolset.Data.Symbols; | ||
| 17 | using WixToolset.Extensibility.Data; | ||
| 18 | using WixToolset.Extensibility.Services; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Retrieve files information and extract them from merge modules. | ||
| 22 | /// </summary> | ||
| 23 | internal class ExtractMergeModuleFilesCommand | ||
| 24 | { | ||
| 25 | public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable<WixMergeSymbol> wixMergeSymbols, IEnumerable<IFileFacade> fileFacades, int installerVersion, string intermediateFolder, bool suppressLayout) | ||
| 26 | { | ||
| 27 | this.Messaging = messaging; | ||
| 28 | this.BackendHelper = backendHelper; | ||
| 29 | this.WixMergeSymbols = wixMergeSymbols; | ||
| 30 | this.FileFacades = fileFacades; | ||
| 31 | this.OutputInstallerVersion = installerVersion; | ||
| 32 | this.IntermediateFolder = intermediateFolder; | ||
| 33 | this.SuppressLayout = suppressLayout; | ||
| 34 | } | ||
| 35 | |||
| 36 | private IMessaging Messaging { get; } | ||
| 37 | |||
| 38 | private IWindowsInstallerBackendHelper BackendHelper { get; } | ||
| 39 | |||
| 40 | private IEnumerable<WixMergeSymbol> WixMergeSymbols { get; } | ||
| 41 | |||
| 42 | private IEnumerable<IFileFacade> FileFacades { get; } | ||
| 43 | |||
| 44 | private int OutputInstallerVersion { get; } | ||
| 45 | |||
| 46 | private string IntermediateFolder { get; } | ||
| 47 | |||
| 48 | private bool SuppressLayout { get; } | ||
| 49 | |||
| 50 | public IEnumerable<IFileFacade> MergeModulesFileFacades { get; private set; } | ||
| 51 | |||
| 52 | public void Execute() | ||
| 53 | { | ||
| 54 | var mergeModulesFileFacades = new List<IFileFacade>(); | ||
| 55 | |||
| 56 | var merge = MsmInterop.GetMsmMerge(); | ||
| 57 | |||
| 58 | // Index all of the file rows to be able to detect collisions with files in the Merge Modules. | ||
| 59 | // It may seem a bit expensive to build up this index solely for the purpose of checking collisions | ||
| 60 | // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out | ||
| 61 | // there are other cases where we need all the file rows indexed, however they are not common cases. | ||
| 62 | // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let | ||
| 63 | // this case be slightly more expensive because the cost of maintaining an indexed file row collection | ||
| 64 | // is a lot more costly for the common cases. | ||
| 65 | var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal); | ||
| 66 | |||
| 67 | foreach (var wixMergeRow in this.WixMergeSymbols) | ||
| 68 | { | ||
| 69 | var containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); | ||
| 70 | |||
| 71 | // If the module has files and creating layout | ||
| 72 | if (containsFiles && !this.SuppressLayout) | ||
| 73 | { | ||
| 74 | this.ExtractFilesFromMergeModule(merge, wixMergeRow); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | this.MergeModulesFileFacades = mergeModulesFileFacades; | ||
| 79 | } | ||
| 80 | |||
| 81 | private bool CreateFacadesForMergeModuleFiles(WixMergeSymbol wixMergeRow, List<IFileFacade> mergeModulesFileFacades, Dictionary<string, IFileFacade> indexedFileFacades) | ||
| 82 | { | ||
| 83 | var containsFiles = false; | ||
| 84 | |||
| 85 | try | ||
| 86 | { | ||
| 87 | // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. | ||
| 88 | using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) | ||
| 89 | { | ||
| 90 | if (db.TableExists("File") && db.TableExists("Component")) | ||
| 91 | { | ||
| 92 | var uniqueModuleFileIdentifiers = new Dictionary<string, IFileFacade>(StringComparer.OrdinalIgnoreCase); | ||
| 93 | |||
| 94 | using (var view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) | ||
| 95 | { | ||
| 96 | // add each file row from the merge module into the file row collection (check for errors along the way) | ||
| 97 | foreach (var record in view.Records) | ||
| 98 | { | ||
| 99 | // NOTE: this is very tricky - the merge module file rows are not added to the | ||
| 100 | // file table because they should not be created via idt import. Instead, these | ||
| 101 | // rows are created by merging in the actual modules. | ||
| 102 | var fileSymbol = new FileSymbol(wixMergeRow.SourceLineNumbers, new Identifier(AccessModifier.Section, record[1])); | ||
| 103 | fileSymbol.Attributes = wixMergeRow.FileAttributes; | ||
| 104 | fileSymbol.DirectoryRef = record[2]; | ||
| 105 | fileSymbol.DiskId = wixMergeRow.DiskId; | ||
| 106 | fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) }; | ||
| 107 | |||
| 108 | var mergeModuleFileFacade = this.BackendHelper.CreateFileFacadeFromMergeModule(fileSymbol); | ||
| 109 | |||
| 110 | // If case-sensitive collision with another merge module or a user-authored file identifier. | ||
| 111 | if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade)) | ||
| 112 | { | ||
| 113 | this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.Id)); | ||
| 114 | } | ||
| 115 | else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module | ||
| 116 | { | ||
| 117 | this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.Id, collidingFacade.Id)); | ||
| 118 | } | ||
| 119 | else // no collision | ||
| 120 | { | ||
| 121 | mergeModulesFileFacades.Add(mergeModuleFileFacade); | ||
| 122 | |||
| 123 | // Keep updating the indexes as new rows are added. | ||
| 124 | indexedFileFacades.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); | ||
| 125 | uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); | ||
| 126 | } | ||
| 127 | |||
| 128 | containsFiles = true; | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | // Get the summary information to detect the Schema | ||
| 134 | using (var summaryInformation = new SummaryInformation(db)) | ||
| 135 | { | ||
| 136 | var moduleInstallerVersionString = summaryInformation.GetProperty(14); | ||
| 137 | |||
| 138 | try | ||
| 139 | { | ||
| 140 | var moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); | ||
| 141 | if (moduleInstallerVersion > this.OutputInstallerVersion) | ||
| 142 | { | ||
| 143 | this.Messaging.Write(WarningMessages.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion)); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | catch (FormatException) | ||
| 147 | { | ||
| 148 | throw new WixException(ErrorMessages.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | catch (FileNotFoundException) | ||
| 154 | { | ||
| 155 | throw new WixException(ErrorMessages.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); | ||
| 156 | } | ||
| 157 | catch (Win32Exception) | ||
| 158 | { | ||
| 159 | throw new WixException(ErrorMessages.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile)); | ||
| 160 | } | ||
| 161 | |||
| 162 | return containsFiles; | ||
| 163 | } | ||
| 164 | |||
| 165 | private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeSymbol wixMergeRow) | ||
| 166 | { | ||
| 167 | var moduleOpen = false; | ||
| 168 | short mergeLanguage; | ||
| 169 | |||
| 170 | var mergeId = wixMergeRow.Id.Id; | ||
| 171 | |||
| 172 | try | ||
| 173 | { | ||
| 174 | mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); | ||
| 175 | } | ||
| 176 | catch (FormatException) | ||
| 177 | { | ||
| 178 | this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, mergeId, wixMergeRow.Language.ToString())); | ||
| 179 | return; | ||
| 180 | } | ||
| 181 | |||
| 182 | try | ||
| 183 | { | ||
| 184 | merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); | ||
| 185 | moduleOpen = true; | ||
| 186 | |||
| 187 | // extract the module cabinet, then explode all of the files to a temp directory | ||
| 188 | var moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab"); | ||
| 189 | merge.ExtractCAB(moduleCabPath); | ||
| 190 | |||
| 191 | var mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId); | ||
| 192 | Directory.CreateDirectory(mergeIdPath); | ||
| 193 | |||
| 194 | try | ||
| 195 | { | ||
| 196 | var cabinet = new Cabinet(moduleCabPath); | ||
| 197 | cabinet.Extract(mergeIdPath); | ||
| 198 | } | ||
| 199 | catch (FileNotFoundException) | ||
| 200 | { | ||
| 201 | throw new WixException(ErrorMessages.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); | ||
| 202 | } | ||
| 203 | catch | ||
| 204 | { | ||
| 205 | throw new WixException(ErrorMessages.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); | ||
| 206 | } | ||
| 207 | } | ||
| 208 | catch (COMException ce) | ||
| 209 | { | ||
| 210 | throw new WixException(ErrorMessages.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); | ||
| 211 | } | ||
| 212 | finally | ||
| 213 | { | ||
| 214 | if (moduleOpen) | ||
| 215 | { | ||
| 216 | merge.CloseModule(); | ||
| 217 | } | ||
| 218 | } | ||
| 219 | } | ||
| 220 | } | ||
| 221 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs new file mode 100644 index 00000000..fe65ccef --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Extensibility; | ||
| 9 | |||
| 10 | internal class FileSystemManager | ||
| 11 | { | ||
| 12 | public FileSystemManager(IEnumerable<IFileSystemExtension> fileSystemExtensions) | ||
| 13 | { | ||
| 14 | this.Extensions = fileSystemExtensions; | ||
| 15 | } | ||
| 16 | |||
| 17 | private IEnumerable<IFileSystemExtension> Extensions { get; } | ||
| 18 | |||
| 19 | public bool CompareFiles(string firstPath, string secondPath) | ||
| 20 | { | ||
| 21 | foreach (var extension in this.Extensions) | ||
| 22 | { | ||
| 23 | var compared = extension.CompareFiles(firstPath, secondPath); | ||
| 24 | if (compared.HasValue) | ||
| 25 | { | ||
| 26 | return compared.Value; | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | return BuiltinCompareFiles(firstPath, secondPath); | ||
| 31 | } | ||
| 32 | |||
| 33 | private static bool BuiltinCompareFiles(string firstPath, string secondPath) | ||
| 34 | { | ||
| 35 | if (String.Equals(firstPath, secondPath, StringComparison.OrdinalIgnoreCase)) | ||
| 36 | { | ||
| 37 | return true; | ||
| 38 | } | ||
| 39 | |||
| 40 | using (var firstStream = File.OpenRead(firstPath)) | ||
| 41 | using (var secondStream = File.OpenRead(secondPath)) | ||
| 42 | { | ||
| 43 | if (firstStream.Length != secondStream.Length) | ||
| 44 | { | ||
| 45 | return false; | ||
| 46 | } | ||
| 47 | |||
| 48 | // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance. | ||
| 49 | // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined. | ||
| 50 | var firstBuffer = new byte[16 * 1024]; | ||
| 51 | var secondBuffer = new byte[16 * 1024]; | ||
| 52 | |||
| 53 | var firstReadLength = 0; | ||
| 54 | do | ||
| 55 | { | ||
| 56 | firstReadLength = firstStream.Read(firstBuffer, 0, firstBuffer.Length); | ||
| 57 | var secondReadLength = secondStream.Read(secondBuffer, 0, secondBuffer.Length); | ||
| 58 | |||
| 59 | if (firstReadLength != secondReadLength) | ||
| 60 | { | ||
| 61 | return false; | ||
| 62 | } | ||
| 63 | |||
| 64 | for (var i = 0; i < firstReadLength; ++i) | ||
| 65 | { | ||
| 66 | if (firstBuffer[i] != secondBuffer[i]) | ||
| 67 | { | ||
| 68 | return false; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } while (0 < firstReadLength); | ||
| 72 | } | ||
| 73 | |||
| 74 | return true; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs new file mode 100644 index 00000000..3cdc0c28 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs | |||
| @@ -0,0 +1,262 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Set the guids for components with generatable guids and validate all are appropriately unique. | ||
| 16 | /// </summary> | ||
| 17 | internal class FinalizeComponentGuids | ||
| 18 | { | ||
| 19 | internal FinalizeComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) | ||
| 20 | { | ||
| 21 | this.Messaging = messaging; | ||
| 22 | this.BackendHelper = helper; | ||
| 23 | this.PathResolver = pathResolver; | ||
| 24 | this.Section = section; | ||
| 25 | this.Platform = platform; | ||
| 26 | } | ||
| 27 | |||
| 28 | private IMessaging Messaging { get; } | ||
| 29 | |||
| 30 | private IBackendHelper BackendHelper { get; } | ||
| 31 | |||
| 32 | private IPathResolver PathResolver { get; } | ||
| 33 | |||
| 34 | private IntermediateSection Section { get; } | ||
| 35 | |||
| 36 | private Platform Platform { get; } | ||
| 37 | |||
| 38 | private Dictionary<string, string> ComponentIdGenSeeds { get; set; } | ||
| 39 | |||
| 40 | private ILookup<string, FileSymbol> FilesByComponentId { get; set; } | ||
| 41 | |||
| 42 | private Dictionary<string, RegistrySymbol> RegistrySymbolsById { get; set; } | ||
| 43 | |||
| 44 | private Dictionary<string, IResolvedDirectory> TargetPathsByDirectoryId { get; set; } | ||
| 45 | |||
| 46 | public void Execute() | ||
| 47 | { | ||
| 48 | var componentGuidConditions = new Dictionary<string, List<ComponentSymbol>>(StringComparer.OrdinalIgnoreCase); | ||
| 49 | var guidCollisions = new HashSet<string>(StringComparer.OrdinalIgnoreCase); | ||
| 50 | |||
| 51 | foreach (var componentSymbol in this.Section.Symbols.OfType<ComponentSymbol>()) | ||
| 52 | { | ||
| 53 | if (componentSymbol.ComponentId == "*") | ||
| 54 | { | ||
| 55 | this.GenerateComponentGuid(componentSymbol); | ||
| 56 | } | ||
| 57 | |||
| 58 | // Now check for GUID collisions, but we don't care about unmanaged components and | ||
| 59 | // if there's a * GUID remaining, there's already an error that explained why it | ||
| 60 | // was not replaced with a real GUID. | ||
| 61 | if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && componentSymbol.ComponentId != "*") | ||
| 62 | { | ||
| 63 | if (!componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var components)) | ||
| 64 | { | ||
| 65 | components = new List<ComponentSymbol>(); | ||
| 66 | componentGuidConditions.Add(componentSymbol.ComponentId, components); | ||
| 67 | } | ||
| 68 | |||
| 69 | components.Add(componentSymbol); | ||
| 70 | if (components.Count > 1) | ||
| 71 | { | ||
| 72 | guidCollisions.Add(componentSymbol.ComponentId); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | if (guidCollisions.Count > 0) | ||
| 78 | { | ||
| 79 | this.ReportGuidCollisions(guidCollisions, componentGuidConditions); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | private void GenerateComponentGuid(ComponentSymbol componentSymbol) | ||
| 84 | { | ||
| 85 | if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType) | ||
| 86 | { | ||
| 87 | this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers)); | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | |||
| 91 | if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType) | ||
| 92 | { | ||
| 93 | if (this.RegistrySymbolsById is null) | ||
| 94 | { | ||
| 95 | this.RegistrySymbolsById = this.Section.Symbols.OfType<RegistrySymbol>().ToDictionary(t => t.Id.Id); | ||
| 96 | } | ||
| 97 | |||
| 98 | if (this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol)) | ||
| 99 | { | ||
| 100 | var bitness = componentSymbol.Win64 ? "64" : String.Empty; | ||
| 101 | var regkey = String.Concat(bitness, registrySymbol.Root, "\\", registrySymbol.Key, "\\", registrySymbol.Name); | ||
| 102 | componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | else // must be a File KeyPath. | ||
| 106 | { | ||
| 107 | // If the directory table hasn't been loaded into an indexed hash | ||
| 108 | // of directory ids to target names do that now. | ||
| 109 | if (this.TargetPathsByDirectoryId is null) | ||
| 110 | { | ||
| 111 | this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); | ||
| 112 | } | ||
| 113 | |||
| 114 | // If the component id generation seeds have not been indexed | ||
| 115 | // from the Directory symbols do that now. | ||
| 116 | if (this.ComponentIdGenSeeds is null) | ||
| 117 | { | ||
| 118 | // If there are any Directory symbols, build up the Component Guid | ||
| 119 | // generation seeds indexed by Directory/@Id. | ||
| 120 | this.ComponentIdGenSeeds = this.Section.Symbols.OfType<DirectorySymbol>() | ||
| 121 | .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) | ||
| 122 | .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); | ||
| 123 | } | ||
| 124 | |||
| 125 | // If the file symbols have not been indexed by File's ComponentRef yet | ||
| 126 | // then do that now. | ||
| 127 | if (this.FilesByComponentId is null) | ||
| 128 | { | ||
| 129 | this.FilesByComponentId = this.Section.Symbols.OfType<FileSymbol>().ToLookup(f => f.ComponentRef); | ||
| 130 | } | ||
| 131 | |||
| 132 | // validate component meets all the conditions to have a generated guid | ||
| 133 | var currentComponentFiles = this.FilesByComponentId[componentSymbol.Id.Id]; | ||
| 134 | var numFilesInComponent = currentComponentFiles.Count(); | ||
| 135 | string path = null; | ||
| 136 | |||
| 137 | foreach (var fileSymbol in currentComponentFiles) | ||
| 138 | { | ||
| 139 | if (fileSymbol.Id.Id == componentSymbol.KeyPath) | ||
| 140 | { | ||
| 141 | // calculate the key file's canonical target path | ||
| 142 | var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, this.ComponentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); | ||
| 143 | var fileName = this.BackendHelper.GetMsiFileName(fileSymbol.Name, false, true).ToLowerInvariant(); | ||
| 144 | path = Path.Combine(directoryPath, fileName); | ||
| 145 | |||
| 146 | // find paths that are not canonicalized | ||
| 147 | if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || | ||
| 148 | path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || | ||
| 149 | path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || | ||
| 150 | path.StartsWith("TARGETDIR", StringComparison.Ordinal) || | ||
| 151 | path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || | ||
| 152 | path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) | ||
| 153 | { | ||
| 154 | this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileSymbol.ComponentRef, path)); | ||
| 155 | } | ||
| 156 | |||
| 157 | // if component has more than one file, the key path must be versioned | ||
| 158 | if (1 < numFilesInComponent && String.IsNullOrEmpty(fileSymbol.Version)) | ||
| 159 | { | ||
| 160 | this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers)); | ||
| 161 | } | ||
| 162 | } | ||
| 163 | else | ||
| 164 | { | ||
| 165 | // not a key path, so it must be an unversioned file if component has more than one file | ||
| 166 | if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileSymbol.Version)) | ||
| 167 | { | ||
| 168 | this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers)); | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | // if the rules were followed, reward with a generated guid | ||
| 174 | if (!this.Messaging.EncounteredError) | ||
| 175 | { | ||
| 176 | componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | private void ReportGuidCollisions(HashSet<string> guidCollisions, Dictionary<string, List<ComponentSymbol>> componentGuidConditions) | ||
| 182 | { | ||
| 183 | Dictionary<string, FileSymbol> fileSymbolsById = null; | ||
| 184 | |||
| 185 | foreach (var guid in guidCollisions) | ||
| 186 | { | ||
| 187 | var collidingComponents = componentGuidConditions[guid]; | ||
| 188 | var allComponentsHaveConditions = collidingComponents.All(c => !String.IsNullOrEmpty(c.Condition)); | ||
| 189 | |||
| 190 | foreach (var componentSymbol in collidingComponents) | ||
| 191 | { | ||
| 192 | string path; | ||
| 193 | string type; | ||
| 194 | |||
| 195 | if (componentSymbol.KeyPathType == ComponentKeyPathType.File) | ||
| 196 | { | ||
| 197 | if (fileSymbolsById is null) | ||
| 198 | { | ||
| 199 | fileSymbolsById = this.Section.Symbols.OfType<FileSymbol>().ToDictionary(t => t.Id.Id); | ||
| 200 | } | ||
| 201 | |||
| 202 | path = fileSymbolsById.TryGetValue(componentSymbol.KeyPath, out var fileSymbol) ? fileSymbol.Source.Path : componentSymbol.KeyPath; | ||
| 203 | type = "source path"; | ||
| 204 | } | ||
| 205 | else if (componentSymbol.KeyPathType == ComponentKeyPathType.Registry) | ||
| 206 | { | ||
| 207 | if (this.RegistrySymbolsById is null) | ||
| 208 | { | ||
| 209 | this.RegistrySymbolsById = this.Section.Symbols.OfType<RegistrySymbol>().ToDictionary(t => t.Id.Id); | ||
| 210 | } | ||
| 211 | |||
| 212 | path = this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol) ? String.Concat(registrySymbol.Key, "\\", registrySymbol.Name) : componentSymbol.KeyPath; | ||
| 213 | type = "registry path"; | ||
| 214 | } | ||
| 215 | else | ||
| 216 | { | ||
| 217 | if (this.TargetPathsByDirectoryId is null) | ||
| 218 | { | ||
| 219 | this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); | ||
| 220 | } | ||
| 221 | |||
| 222 | path = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, componentIdGenSeeds: null, componentSymbol.DirectoryRef, this.Platform); | ||
| 223 | type = "directory"; | ||
| 224 | } | ||
| 225 | |||
| 226 | if (allComponentsHaveConditions) | ||
| 227 | { | ||
| 228 | this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); | ||
| 229 | } | ||
| 230 | else | ||
| 231 | { | ||
| 232 | this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | private Dictionary<string, IResolvedDirectory> ResolveDirectoryTargetPaths() | ||
| 239 | { | ||
| 240 | var directories = this.Section.Symbols.OfType<DirectorySymbol>().ToList(); | ||
| 241 | |||
| 242 | var targetPathsByDirectoryId = new Dictionary<string, IResolvedDirectory>(directories.Count); | ||
| 243 | |||
| 244 | // Get the target paths for all directories. | ||
| 245 | foreach (var directory in directories) | ||
| 246 | { | ||
| 247 | // If the directory Id already exists, we will skip it here since | ||
| 248 | // checking for duplicate primary keys is done later when importing tables | ||
| 249 | // into database | ||
| 250 | if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) | ||
| 251 | { | ||
| 252 | continue; | ||
| 253 | } | ||
| 254 | |||
| 255 | var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); | ||
| 256 | targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); | ||
| 257 | } | ||
| 258 | |||
| 259 | return targetPathsByDirectoryId; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs new file mode 100644 index 00000000..b8cca752 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs | |||
| @@ -0,0 +1,408 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.ComponentModel; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Text; | ||
| 11 | using WixToolset.Core.Native.Msi; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.WindowsInstaller; | ||
| 14 | using WixToolset.Extensibility.Data; | ||
| 15 | using WixToolset.Extensibility.Services; | ||
| 16 | |||
| 17 | internal class GenerateDatabaseCommand | ||
| 18 | { | ||
| 19 | private const string IdtsSubFolder = "_idts"; | ||
| 20 | |||
| 21 | public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) | ||
| 22 | { | ||
| 23 | this.Messaging = messaging; | ||
| 24 | this.BackendHelper = backendHelper; | ||
| 25 | this.FileSystemManager = fileSystemManager; | ||
| 26 | this.Data = data; | ||
| 27 | this.OutputPath = outputPath; | ||
| 28 | this.TableDefinitions = tableDefinitions; | ||
| 29 | this.IntermediateFolder = intermediateFolder; | ||
| 30 | this.KeepAddedColumns = keepAddedColumns; | ||
| 31 | this.SuppressAddingValidationRows = suppressAddingValidationRows; | ||
| 32 | this.UseSubDirectory = useSubdirectory; | ||
| 33 | } | ||
| 34 | |||
| 35 | private IBackendHelper BackendHelper { get; } | ||
| 36 | |||
| 37 | private FileSystemManager FileSystemManager { get; } | ||
| 38 | |||
| 39 | /// <summary> | ||
| 40 | /// Whether to keep columns added in a transform. | ||
| 41 | /// </summary> | ||
| 42 | private bool KeepAddedColumns { get; } | ||
| 43 | |||
| 44 | private IMessaging Messaging { get; } | ||
| 45 | |||
| 46 | private WindowsInstallerData Data { get; } | ||
| 47 | |||
| 48 | private string OutputPath { get; } | ||
| 49 | |||
| 50 | private TableDefinitionCollection TableDefinitions { get; } | ||
| 51 | |||
| 52 | private string IntermediateFolder { get; } | ||
| 53 | |||
| 54 | public List<ITrackedFile> GeneratedTemporaryFiles { get; } = new List<ITrackedFile>(); | ||
| 55 | |||
| 56 | /// <summary> | ||
| 57 | /// Whether to use a subdirectory based on the database file name for intermediate files. | ||
| 58 | /// </summary> | ||
| 59 | private bool SuppressAddingValidationRows { get; } | ||
| 60 | |||
| 61 | private bool UseSubDirectory { get; } | ||
| 62 | |||
| 63 | public void Execute() | ||
| 64 | { | ||
| 65 | // Add the _Validation rows. | ||
| 66 | if (!this.SuppressAddingValidationRows) | ||
| 67 | { | ||
| 68 | this.AddValidationRows(); | ||
| 69 | } | ||
| 70 | |||
| 71 | var baseDirectory = this.IntermediateFolder; | ||
| 72 | |||
| 73 | if (this.UseSubDirectory) | ||
| 74 | { | ||
| 75 | var filename = Path.GetFileNameWithoutExtension(this.OutputPath); | ||
| 76 | baseDirectory = Path.Combine(baseDirectory, filename); | ||
| 77 | } | ||
| 78 | |||
| 79 | var idtFolder = Path.Combine(baseDirectory, IdtsSubFolder); | ||
| 80 | |||
| 81 | var type = OpenDatabase.CreateDirect; | ||
| 82 | |||
| 83 | if (OutputType.Patch == this.Data.Type) | ||
| 84 | { | ||
| 85 | type |= OpenDatabase.OpenPatchFile; | ||
| 86 | } | ||
| 87 | |||
| 88 | try | ||
| 89 | { | ||
| 90 | Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); | ||
| 91 | |||
| 92 | Directory.CreateDirectory(idtFolder); | ||
| 93 | |||
| 94 | using (var db = new Database(this.OutputPath, type)) | ||
| 95 | { | ||
| 96 | // If we're not using the default codepage, import a new one into our | ||
| 97 | // database before we add any tables (or the tables would be added | ||
| 98 | // with the wrong codepage). | ||
| 99 | if (0 != this.Data.Codepage) | ||
| 100 | { | ||
| 101 | this.SetDatabaseCodepage(db, this.Data.Codepage, idtFolder); | ||
| 102 | } | ||
| 103 | |||
| 104 | this.ImportTables(db, idtFolder); | ||
| 105 | |||
| 106 | // Insert substorages (usually transforms inside a patch or instance transforms in a package). | ||
| 107 | this.ImportSubStorages(db); | ||
| 108 | |||
| 109 | // We're good, commit the changes to the new database. | ||
| 110 | db.Commit(); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | catch (IOException e) | ||
| 114 | { | ||
| 115 | // TODO: this error message doesn't seem specific enough | ||
| 116 | throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | private void AddValidationRows() | ||
| 121 | { | ||
| 122 | var validationTable = this.Data.EnsureTable(this.TableDefinitions["_Validation"]); | ||
| 123 | |||
| 124 | // Add the validation rows for real tables and columns. | ||
| 125 | foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) | ||
| 126 | { | ||
| 127 | foreach (var columnDef in table.Definition.Columns.Where(c => !c.Unreal)) | ||
| 128 | { | ||
| 129 | var row = validationTable.CreateRow(null); | ||
| 130 | |||
| 131 | row[0] = table.Name; | ||
| 132 | |||
| 133 | row[1] = columnDef.Name; | ||
| 134 | |||
| 135 | if (columnDef.Nullable) | ||
| 136 | { | ||
| 137 | row[2] = "Y"; | ||
| 138 | } | ||
| 139 | else | ||
| 140 | { | ||
| 141 | row[2] = "N"; | ||
| 142 | } | ||
| 143 | |||
| 144 | if (columnDef.MinValue.HasValue) | ||
| 145 | { | ||
| 146 | row[3] = columnDef.MinValue.Value; | ||
| 147 | } | ||
| 148 | |||
| 149 | if (columnDef.MaxValue.HasValue) | ||
| 150 | { | ||
| 151 | row[4] = columnDef.MaxValue.Value; | ||
| 152 | } | ||
| 153 | |||
| 154 | row[5] = columnDef.KeyTable; | ||
| 155 | |||
| 156 | if (columnDef.KeyColumn.HasValue) | ||
| 157 | { | ||
| 158 | row[6] = columnDef.KeyColumn.Value; | ||
| 159 | } | ||
| 160 | |||
| 161 | if (ColumnCategory.Unknown != columnDef.Category) | ||
| 162 | { | ||
| 163 | row[7] = columnDef.Category.ToString(); | ||
| 164 | } | ||
| 165 | |||
| 166 | row[8] = columnDef.Possibilities; | ||
| 167 | |||
| 168 | row[9] = columnDef.Description; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | private void ImportTables(Database db, string idtDirectory) | ||
| 174 | { | ||
| 175 | foreach (var table in this.Data.Tables) | ||
| 176 | { | ||
| 177 | var importTable = table; | ||
| 178 | var hasBinaryColumn = false; | ||
| 179 | |||
| 180 | // Skip all unreal tables other than _Streams. | ||
| 181 | if (table.Definition.Unreal && "_Streams" != table.Name) | ||
| 182 | { | ||
| 183 | continue; | ||
| 184 | } | ||
| 185 | |||
| 186 | // Do not put the _Validation table in patches, it is not needed. | ||
| 187 | if (OutputType.Patch == this.Data.Type && "_Validation" == table.Name) | ||
| 188 | { | ||
| 189 | continue; | ||
| 190 | } | ||
| 191 | |||
| 192 | // The only way to import binary data is to copy it to a local subdirectory first. | ||
| 193 | // To avoid this extra copying and perf hit, import an empty table with the same | ||
| 194 | // definition and later import the binary data from source using records. | ||
| 195 | foreach (var columnDefinition in table.Definition.Columns) | ||
| 196 | { | ||
| 197 | if (ColumnType.Object == columnDefinition.Type) | ||
| 198 | { | ||
| 199 | importTable = new Table(table.Definition); | ||
| 200 | hasBinaryColumn = true; | ||
| 201 | break; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | // Create the table via IDT import. | ||
| 206 | if ("_Streams" != importTable.Name) | ||
| 207 | { | ||
| 208 | try | ||
| 209 | { | ||
| 210 | var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Data.Codepage, idtDirectory, this.KeepAddedColumns); | ||
| 211 | command.Execute(); | ||
| 212 | |||
| 213 | var trackIdt = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary); | ||
| 214 | this.GeneratedTemporaryFiles.Add(trackIdt); | ||
| 215 | |||
| 216 | db.Import(command.IdtPath); | ||
| 217 | } | ||
| 218 | catch (WixInvalidIdtException) | ||
| 219 | { | ||
| 220 | // If ValidateRows finds anything it doesn't like, it throws | ||
| 221 | importTable.ValidateRows(); | ||
| 222 | |||
| 223 | // Otherwise we rethrow the InvalidIdt | ||
| 224 | throw; | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | // insert the rows via SQL query if this table contains object fields | ||
| 229 | if (hasBinaryColumn) | ||
| 230 | { | ||
| 231 | var query = new StringBuilder("SELECT "); | ||
| 232 | |||
| 233 | // Build the query for the view. | ||
| 234 | var firstColumn = true; | ||
| 235 | foreach (var columnDefinition in table.Definition.Columns) | ||
| 236 | { | ||
| 237 | if (columnDefinition.Unreal) | ||
| 238 | { | ||
| 239 | continue; | ||
| 240 | } | ||
| 241 | |||
| 242 | if (!firstColumn) | ||
| 243 | { | ||
| 244 | query.Append(","); | ||
| 245 | } | ||
| 246 | |||
| 247 | query.AppendFormat(" `{0}`", columnDefinition.Name); | ||
| 248 | firstColumn = false; | ||
| 249 | } | ||
| 250 | query.AppendFormat(" FROM `{0}`", table.Name); | ||
| 251 | |||
| 252 | using (var tableView = db.OpenExecuteView(query.ToString())) | ||
| 253 | { | ||
| 254 | // Import each row containing a stream | ||
| 255 | foreach (var row in table.Rows) | ||
| 256 | { | ||
| 257 | using (var record = new Record(table.Definition.Columns.Length)) | ||
| 258 | { | ||
| 259 | // Stream names are created by concatenating the name of the table with the values | ||
| 260 | // of the primary key (delimited by periods). | ||
| 261 | var streamName = new StringBuilder(); | ||
| 262 | |||
| 263 | // the _Streams table doesn't prepend the table name (or a period) | ||
| 264 | if ("_Streams" != table.Name) | ||
| 265 | { | ||
| 266 | streamName.Append(table.Name); | ||
| 267 | } | ||
| 268 | |||
| 269 | var needStream = false; | ||
| 270 | |||
| 271 | for (var i = 0; i < table.Definition.Columns.Length; i++) | ||
| 272 | { | ||
| 273 | var columnDefinition = table.Definition.Columns[i]; | ||
| 274 | |||
| 275 | if (columnDefinition.Unreal) | ||
| 276 | { | ||
| 277 | continue; | ||
| 278 | } | ||
| 279 | |||
| 280 | switch (columnDefinition.Type) | ||
| 281 | { | ||
| 282 | case ColumnType.Localized: | ||
| 283 | case ColumnType.Preserved: | ||
| 284 | case ColumnType.String: | ||
| 285 | var str = row.FieldAsString(i); | ||
| 286 | |||
| 287 | if (columnDefinition.PrimaryKey) | ||
| 288 | { | ||
| 289 | if (0 < streamName.Length) | ||
| 290 | { | ||
| 291 | streamName.Append("."); | ||
| 292 | } | ||
| 293 | |||
| 294 | streamName.Append(str); | ||
| 295 | } | ||
| 296 | |||
| 297 | record.SetString(i + 1, str); | ||
| 298 | break; | ||
| 299 | case ColumnType.Number: | ||
| 300 | record.SetInteger(i + 1, row.FieldAsInteger(i)); | ||
| 301 | break; | ||
| 302 | |||
| 303 | case ColumnType.Object: | ||
| 304 | var path = row.FieldAsString(i); | ||
| 305 | if (null != path) | ||
| 306 | { | ||
| 307 | needStream = true; | ||
| 308 | try | ||
| 309 | { | ||
| 310 | record.SetStream(i + 1, path); | ||
| 311 | } | ||
| 312 | catch (Win32Exception e) | ||
| 313 | { | ||
| 314 | if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME | ||
| 315 | { | ||
| 316 | throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path)); | ||
| 317 | } | ||
| 318 | else | ||
| 319 | { | ||
| 320 | throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | break; | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | // check for a stream name that is more than 62 characters long (the maximum allowed length) | ||
| 329 | if (needStream && Database.MsiMaxStreamNameLength < streamName.Length) | ||
| 330 | { | ||
| 331 | this.Messaging.Write(ErrorMessages.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); | ||
| 332 | } | ||
| 333 | else // add the row to the database | ||
| 334 | { | ||
| 335 | tableView.Modify(ModifyView.Assign, record); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | // Remove rows from the _Streams table for wixpdbs. | ||
| 342 | if ("_Streams" == table.Name) | ||
| 343 | { | ||
| 344 | table.Rows.Clear(); | ||
| 345 | } | ||
| 346 | } | ||
| 347 | } | ||
| 348 | } | ||
| 349 | |||
| 350 | private void ImportSubStorages(Database db) | ||
| 351 | { | ||
| 352 | if (0 < this.Data.SubStorages.Count) | ||
| 353 | { | ||
| 354 | using (var storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) | ||
| 355 | { | ||
| 356 | foreach (var subStorage in this.Data.SubStorages) | ||
| 357 | { | ||
| 358 | var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst")); | ||
| 359 | |||
| 360 | // Bind the transform. | ||
| 361 | var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions); | ||
| 362 | command.Execute(); | ||
| 363 | |||
| 364 | if (this.Messaging.EncounteredError) | ||
| 365 | { | ||
| 366 | continue; | ||
| 367 | } | ||
| 368 | |||
| 369 | // Add the storage to the database. | ||
| 370 | using (var record = new Record(2)) | ||
| 371 | { | ||
| 372 | record.SetString(1, subStorage.Name); | ||
| 373 | record.SetStream(2, transformFile); | ||
| 374 | storagesView.Modify(ModifyView.Assign, record); | ||
| 375 | } | ||
| 376 | } | ||
| 377 | } | ||
| 378 | } | ||
| 379 | } | ||
| 380 | |||
| 381 | private void SetDatabaseCodepage(Database db, int codepage, string idtFolder) | ||
| 382 | { | ||
| 383 | // Write out the _ForceCodepage IDT file. | ||
| 384 | var idtPath = Path.Combine(idtFolder, "_ForceCodepage.idt"); | ||
| 385 | using (var idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) | ||
| 386 | { | ||
| 387 | idtFile.WriteLine(); // dummy column name record | ||
| 388 | idtFile.WriteLine(); // dummy column definition record | ||
| 389 | idtFile.Write(codepage); | ||
| 390 | idtFile.WriteLine("\t_ForceCodepage"); | ||
| 391 | } | ||
| 392 | |||
| 393 | var trackIdt = this.BackendHelper.TrackFile(idtPath, TrackedFileType.Temporary); | ||
| 394 | this.GeneratedTemporaryFiles.Add(trackIdt); | ||
| 395 | |||
| 396 | // Try to import the table into the MSI. | ||
| 397 | try | ||
| 398 | { | ||
| 399 | db.Import(idtPath); | ||
| 400 | } | ||
| 401 | catch (WixInvalidIdtException) | ||
| 402 | { | ||
| 403 | // The IDT should always be generated correctly, so an invalid code page was given. | ||
| 404 | throw new WixException(ErrorMessages.IllegalCodepage(codepage)); | ||
| 405 | } | ||
| 406 | } | ||
| 407 | } | ||
| 408 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs new file mode 100644 index 00000000..faa03762 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs | |||
| @@ -0,0 +1,582 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using WixToolset.Core.Native.Msi; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Data.WindowsInstaller; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Creates a transform by diffing two outputs. | ||
| 16 | /// </summary> | ||
| 17 | internal class GenerateTransformCommand | ||
| 18 | { | ||
| 19 | private const char sectionDelimiter = '/'; | ||
| 20 | private readonly IMessaging messaging; | ||
| 21 | private SummaryInformationStreams transformSummaryInfo; | ||
| 22 | |||
| 23 | /// <summary> | ||
| 24 | /// Instantiates a new Differ class. | ||
| 25 | /// </summary> | ||
| 26 | public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages) | ||
| 27 | { | ||
| 28 | this.messaging = messaging; | ||
| 29 | this.TargetOutput = targetOutput; | ||
| 30 | this.UpdatedOutput = updatedOutput; | ||
| 31 | this.PreserveUnchangedRows = preserveUnchangedRows; | ||
| 32 | this.ShowPedanticMessages = showPedanticMessages; | ||
| 33 | } | ||
| 34 | |||
| 35 | private WindowsInstallerData TargetOutput { get; } | ||
| 36 | |||
| 37 | private WindowsInstallerData UpdatedOutput { get; } | ||
| 38 | |||
| 39 | private TransformFlags ValidationFlags { get; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets or sets the option to show pedantic messages. | ||
| 43 | /// </summary> | ||
| 44 | /// <value>The option to show pedantic messages.</value> | ||
| 45 | private bool ShowPedanticMessages { get; } | ||
| 46 | |||
| 47 | /// <summary> | ||
| 48 | /// Gets or sets the option to suppress keeping special rows. | ||
| 49 | /// </summary> | ||
| 50 | /// <value>The option to suppress keeping special rows.</value> | ||
| 51 | private bool SuppressKeepingSpecialRows { get; } | ||
| 52 | |||
| 53 | /// <summary> | ||
| 54 | /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. | ||
| 55 | /// </summary> | ||
| 56 | /// <value>The option to keep all rows including unchanged rows.</value> | ||
| 57 | private bool PreserveUnchangedRows { get; } | ||
| 58 | |||
| 59 | public WindowsInstallerData Transform { get; private set; } | ||
| 60 | |||
| 61 | /// <summary> | ||
| 62 | /// Creates a transform by diffing two outputs. | ||
| 63 | /// </summary> | ||
| 64 | public WindowsInstallerData Execute() | ||
| 65 | { | ||
| 66 | var targetOutput = this.TargetOutput; | ||
| 67 | var updatedOutput = this.UpdatedOutput; | ||
| 68 | var validationFlags = this.ValidationFlags; | ||
| 69 | |||
| 70 | var transform = new WindowsInstallerData(null) | ||
| 71 | { | ||
| 72 | Type = OutputType.Transform, | ||
| 73 | Codepage = updatedOutput.Codepage | ||
| 74 | }; | ||
| 75 | |||
| 76 | this.transformSummaryInfo = new SummaryInformationStreams(); | ||
| 77 | |||
| 78 | // compare the codepages | ||
| 79 | if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) | ||
| 80 | { | ||
| 81 | this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); | ||
| 82 | if (null != updatedOutput.SourceLineNumbers) | ||
| 83 | { | ||
| 84 | this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | // compare the output types | ||
| 89 | if (targetOutput.Type != updatedOutput.Type) | ||
| 90 | { | ||
| 91 | throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); | ||
| 92 | } | ||
| 93 | |||
| 94 | // compare the contents of the tables | ||
| 95 | foreach (var targetTable in targetOutput.Tables) | ||
| 96 | { | ||
| 97 | var updatedTable = updatedOutput.Tables[targetTable.Name]; | ||
| 98 | var operation = TableOperation.None; | ||
| 99 | |||
| 100 | var rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); | ||
| 101 | |||
| 102 | if (TableOperation.Drop == operation) | ||
| 103 | { | ||
| 104 | var droppedTable = transform.EnsureTable(targetTable.Definition); | ||
| 105 | droppedTable.Operation = TableOperation.Drop; | ||
| 106 | } | ||
| 107 | else if (TableOperation.None == operation) | ||
| 108 | { | ||
| 109 | var modifiedTable = transform.EnsureTable(updatedTable.Definition); | ||
| 110 | foreach (var row in rows) | ||
| 111 | { | ||
| 112 | modifiedTable.Rows.Add(row); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | // added tables | ||
| 118 | foreach (var updatedTable in updatedOutput.Tables) | ||
| 119 | { | ||
| 120 | if (null == targetOutput.Tables[updatedTable.Name]) | ||
| 121 | { | ||
| 122 | var addedTable = transform.EnsureTable(updatedTable.Definition); | ||
| 123 | addedTable.Operation = TableOperation.Add; | ||
| 124 | |||
| 125 | foreach (var updatedRow in updatedTable.Rows) | ||
| 126 | { | ||
| 127 | updatedRow.Operation = RowOperation.Add; | ||
| 128 | updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; | ||
| 129 | addedTable.Rows.Add(updatedRow); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | // set summary information properties | ||
| 135 | if (!this.SuppressKeepingSpecialRows) | ||
| 136 | { | ||
| 137 | var summaryInfoTable = transform.Tables["_SummaryInformation"]; | ||
| 138 | this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); | ||
| 139 | } | ||
| 140 | |||
| 141 | this.Transform = transform; | ||
| 142 | return this.Transform; | ||
| 143 | } | ||
| 144 | |||
| 145 | /// <summary> | ||
| 146 | /// Add a row to the <paramref name="index"/> using the primary key. | ||
| 147 | /// </summary> | ||
| 148 | /// <param name="index">The indexed rows.</param> | ||
| 149 | /// <param name="row">The row to index.</param> | ||
| 150 | private void AddIndexedRow(Dictionary<string, Row> index, Row row) | ||
| 151 | { | ||
| 152 | var primaryKey = row.GetPrimaryKey(); | ||
| 153 | |||
| 154 | if (null != primaryKey) | ||
| 155 | { | ||
| 156 | if (index.TryGetValue(primaryKey, out var collisionRow)) | ||
| 157 | { | ||
| 158 | #if TODO_PATCH // This case doesn't seem like it can happen any longer. | ||
| 159 | // Overriding WixActionRows have a primary key defined and take precedence in the index. | ||
| 160 | if (row is WixActionRow actionRow) | ||
| 161 | { | ||
| 162 | // If the current row is not overridable, see if the indexed row is. | ||
| 163 | if (!actionRow.Overridable) | ||
| 164 | { | ||
| 165 | if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable) | ||
| 166 | { | ||
| 167 | // The indexed key is overridable and should be replaced. | ||
| 168 | index[primaryKey] = actionRow; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | // If we got this far, the row does not need to be indexed. | ||
| 173 | return; | ||
| 174 | } | ||
| 175 | #endif | ||
| 176 | |||
| 177 | if (this.ShowPedanticMessages) | ||
| 178 | { | ||
| 179 | this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); | ||
| 180 | } | ||
| 181 | } | ||
| 182 | else | ||
| 183 | { | ||
| 184 | index.Add(primaryKey, row); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | else // use the string representation of the row as its primary key (it may not be unique) | ||
| 188 | { | ||
| 189 | // this is provided for compatibility with unreal tables with no primary key | ||
| 190 | // all real tables must specify at least one column as the primary key | ||
| 191 | primaryKey = row.ToString(); | ||
| 192 | index[primaryKey] = row; | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | private bool CompareRows(Table targetTable, Row targetRow, Row updatedRow, out Row comparedRow) | ||
| 197 | { | ||
| 198 | comparedRow = null; | ||
| 199 | |||
| 200 | var keepRow = false; | ||
| 201 | |||
| 202 | if (null == targetRow ^ null == updatedRow) | ||
| 203 | { | ||
| 204 | if (null == targetRow) | ||
| 205 | { | ||
| 206 | updatedRow.Operation = RowOperation.Add; | ||
| 207 | comparedRow = updatedRow; | ||
| 208 | } | ||
| 209 | else if (null == updatedRow) | ||
| 210 | { | ||
| 211 | targetRow.Operation = RowOperation.Delete; | ||
| 212 | targetRow.SectionId += sectionDelimiter; | ||
| 213 | |||
| 214 | comparedRow = targetRow; | ||
| 215 | keepRow = true; | ||
| 216 | } | ||
| 217 | } | ||
| 218 | else // possibly modified | ||
| 219 | { | ||
| 220 | updatedRow.Operation = RowOperation.None; | ||
| 221 | if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) | ||
| 222 | { | ||
| 223 | // ignore rows that shouldn't be in a transform | ||
| 224 | if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) | ||
| 225 | { | ||
| 226 | updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; | ||
| 227 | comparedRow = updatedRow; | ||
| 228 | keepRow = true; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | else | ||
| 232 | { | ||
| 233 | if (this.PreserveUnchangedRows) | ||
| 234 | { | ||
| 235 | keepRow = true; | ||
| 236 | } | ||
| 237 | |||
| 238 | for (var i = 0; i < updatedRow.Fields.Length; i++) | ||
| 239 | { | ||
| 240 | var columnDefinition = updatedRow.Fields[i].Column; | ||
| 241 | |||
| 242 | if (!columnDefinition.PrimaryKey) | ||
| 243 | { | ||
| 244 | var modified = false; | ||
| 245 | |||
| 246 | if (i >= targetRow.Fields.Length) | ||
| 247 | { | ||
| 248 | columnDefinition.Added = true; | ||
| 249 | modified = true; | ||
| 250 | } | ||
| 251 | else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) | ||
| 252 | { | ||
| 253 | if (null == targetRow[i] ^ null == updatedRow[i]) | ||
| 254 | { | ||
| 255 | modified = true; | ||
| 256 | } | ||
| 257 | else if (null != targetRow[i] && null != updatedRow[i]) | ||
| 258 | { | ||
| 259 | modified = (targetRow.FieldAsInteger(i) != updatedRow.FieldAsInteger(i)); | ||
| 260 | } | ||
| 261 | } | ||
| 262 | else if (ColumnType.Preserved == columnDefinition.Type) | ||
| 263 | { | ||
| 264 | updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); | ||
| 265 | |||
| 266 | // keep rows containing preserved fields so the historical data is available to the binder | ||
| 267 | keepRow = !this.SuppressKeepingSpecialRows; | ||
| 268 | } | ||
| 269 | else if (ColumnType.Object == columnDefinition.Type) | ||
| 270 | { | ||
| 271 | var targetObjectField = (ObjectField)targetRow.Fields[i]; | ||
| 272 | var updatedObjectField = (ObjectField)updatedRow.Fields[i]; | ||
| 273 | |||
| 274 | updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; | ||
| 275 | updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; | ||
| 276 | |||
| 277 | // always keep a copy of the previous data even if they are identical | ||
| 278 | // This makes diff.wixmst clean and easier to control patch logic | ||
| 279 | updatedObjectField.PreviousData = (string)targetObjectField.Data; | ||
| 280 | |||
| 281 | // always remember the unresolved data for target build | ||
| 282 | updatedObjectField.UnresolvedPreviousData = targetObjectField.UnresolvedData; | ||
| 283 | |||
| 284 | // keep rows containing object fields so the files can be compared in the binder | ||
| 285 | keepRow = !this.SuppressKeepingSpecialRows; | ||
| 286 | } | ||
| 287 | else | ||
| 288 | { | ||
| 289 | modified = (targetRow.FieldAsString(i) != updatedRow.FieldAsString(i)); | ||
| 290 | } | ||
| 291 | |||
| 292 | if (modified) | ||
| 293 | { | ||
| 294 | if (null != updatedRow.Fields[i].PreviousData) | ||
| 295 | { | ||
| 296 | updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); | ||
| 297 | } | ||
| 298 | |||
| 299 | updatedRow.Fields[i].Modified = true; | ||
| 300 | updatedRow.Operation = RowOperation.Modify; | ||
| 301 | keepRow = true; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | if (keepRow) | ||
| 307 | { | ||
| 308 | comparedRow = updatedRow; | ||
| 309 | comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; | ||
| 310 | } | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | return keepRow; | ||
| 315 | } | ||
| 316 | |||
| 317 | private List<Row> CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) | ||
| 318 | { | ||
| 319 | var rows = new List<Row>(); | ||
| 320 | operation = TableOperation.None; | ||
| 321 | |||
| 322 | // dropped tables | ||
| 323 | if (null == updatedTable ^ null == targetTable) | ||
| 324 | { | ||
| 325 | if (null == targetTable) | ||
| 326 | { | ||
| 327 | operation = TableOperation.Add; | ||
| 328 | rows.AddRange(updatedTable.Rows); | ||
| 329 | } | ||
| 330 | else if (null == updatedTable) | ||
| 331 | { | ||
| 332 | operation = TableOperation.Drop; | ||
| 333 | } | ||
| 334 | } | ||
| 335 | else // possibly modified tables | ||
| 336 | { | ||
| 337 | var updatedPrimaryKeys = new Dictionary<string, Row>(); | ||
| 338 | var targetPrimaryKeys = new Dictionary<string, Row>(); | ||
| 339 | |||
| 340 | // compare the table definitions | ||
| 341 | if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) | ||
| 342 | { | ||
| 343 | // continue to the next table; may be more mismatches | ||
| 344 | this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); | ||
| 345 | } | ||
| 346 | else | ||
| 347 | { | ||
| 348 | this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); | ||
| 349 | |||
| 350 | // diff the target and updated rows | ||
| 351 | foreach (var targetPrimaryKeyEntry in targetPrimaryKeys) | ||
| 352 | { | ||
| 353 | var targetPrimaryKey = targetPrimaryKeyEntry.Key; | ||
| 354 | var targetRow = targetPrimaryKeyEntry.Value; | ||
| 355 | updatedPrimaryKeys.TryGetValue(targetPrimaryKey, out var updatedRow); | ||
| 356 | |||
| 357 | var keepRow = this.CompareRows(targetTable, targetRow, updatedRow, out var compared); | ||
| 358 | |||
| 359 | if (keepRow) | ||
| 360 | { | ||
| 361 | rows.Add(compared); | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | // find the inserted rows | ||
| 366 | foreach (var updatedPrimaryKeyEntry in updatedPrimaryKeys) | ||
| 367 | { | ||
| 368 | var updatedPrimaryKey = updatedPrimaryKeyEntry.Key; | ||
| 369 | |||
| 370 | if (!targetPrimaryKeys.ContainsKey(updatedPrimaryKey)) | ||
| 371 | { | ||
| 372 | var updatedRow = updatedPrimaryKeyEntry.Value; | ||
| 373 | |||
| 374 | updatedRow.Operation = RowOperation.Add; | ||
| 375 | updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; | ||
| 376 | rows.Add(updatedRow); | ||
| 377 | } | ||
| 378 | } | ||
| 379 | } | ||
| 380 | } | ||
| 381 | |||
| 382 | return rows; | ||
| 383 | } | ||
| 384 | |||
| 385 | private void IndexPrimaryKeys(Table targetTable, Dictionary<string, Row> targetPrimaryKeys, Table updatedTable, Dictionary<string, Row> updatedPrimaryKeys) | ||
| 386 | { | ||
| 387 | // index the target rows | ||
| 388 | foreach (var row in targetTable.Rows) | ||
| 389 | { | ||
| 390 | this.AddIndexedRow(targetPrimaryKeys, row); | ||
| 391 | |||
| 392 | if ("Property" == targetTable.Name) | ||
| 393 | { | ||
| 394 | var id = row.FieldAsString(0); | ||
| 395 | |||
| 396 | if ("ProductCode" == id) | ||
| 397 | { | ||
| 398 | this.transformSummaryInfo.TargetProductCode = row.FieldAsString(1); | ||
| 399 | |||
| 400 | if ("*" == this.transformSummaryInfo.TargetProductCode) | ||
| 401 | { | ||
| 402 | this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); | ||
| 403 | } | ||
| 404 | } | ||
| 405 | else if ("ProductVersion" == id) | ||
| 406 | { | ||
| 407 | this.transformSummaryInfo.TargetProductVersion = row.FieldAsString(1); | ||
| 408 | } | ||
| 409 | else if ("UpgradeCode" == id) | ||
| 410 | { | ||
| 411 | this.transformSummaryInfo.TargetUpgradeCode = row.FieldAsString(1); | ||
| 412 | } | ||
| 413 | } | ||
| 414 | else if ("_SummaryInformation" == targetTable.Name) | ||
| 415 | { | ||
| 416 | var id = row.FieldAsInteger(0); | ||
| 417 | |||
| 418 | if (1 == id) // PID_CODEPAGE | ||
| 419 | { | ||
| 420 | this.transformSummaryInfo.TargetSummaryInfoCodepage = row.FieldAsString(1); | ||
| 421 | } | ||
| 422 | else if (7 == id) // PID_TEMPLATE | ||
| 423 | { | ||
| 424 | this.transformSummaryInfo.TargetPlatformAndLanguage = row.FieldAsString(1); | ||
| 425 | } | ||
| 426 | else if (14 == id) // PID_PAGECOUNT | ||
| 427 | { | ||
| 428 | this.transformSummaryInfo.TargetMinimumVersion = row.FieldAsString(1); | ||
| 429 | } | ||
| 430 | } | ||
| 431 | } | ||
| 432 | |||
| 433 | // index the updated rows | ||
| 434 | foreach (var row in updatedTable.Rows) | ||
| 435 | { | ||
| 436 | this.AddIndexedRow(updatedPrimaryKeys, row); | ||
| 437 | |||
| 438 | if ("Property" == updatedTable.Name) | ||
| 439 | { | ||
| 440 | var id = row.FieldAsString(0); | ||
| 441 | |||
| 442 | if ("ProductCode" == id) | ||
| 443 | { | ||
| 444 | this.transformSummaryInfo.UpdatedProductCode = row.FieldAsString(1); | ||
| 445 | |||
| 446 | if ("*" == this.transformSummaryInfo.UpdatedProductCode) | ||
| 447 | { | ||
| 448 | this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); | ||
| 449 | } | ||
| 450 | } | ||
| 451 | else if ("ProductVersion" == id) | ||
| 452 | { | ||
| 453 | this.transformSummaryInfo.UpdatedProductVersion = row.FieldAsString(1); | ||
| 454 | } | ||
| 455 | } | ||
| 456 | else if ("_SummaryInformation" == updatedTable.Name) | ||
| 457 | { | ||
| 458 | var id = row.FieldAsInteger(0); | ||
| 459 | |||
| 460 | if (1 == id) // PID_CODEPAGE | ||
| 461 | { | ||
| 462 | this.transformSummaryInfo.UpdatedSummaryInfoCodepage = row.FieldAsString(1); | ||
| 463 | } | ||
| 464 | else if (7 == id) // PID_TEMPLATE | ||
| 465 | { | ||
| 466 | this.transformSummaryInfo.UpdatedPlatformAndLanguage = row.FieldAsString(1); | ||
| 467 | } | ||
| 468 | else if (14 == id) // PID_PAGECOUNT | ||
| 469 | { | ||
| 470 | this.transformSummaryInfo.UpdatedMinimumVersion = row.FieldAsString(1); | ||
| 471 | } | ||
| 472 | } | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) | ||
| 477 | { | ||
| 478 | // calculate the minimum version of MSI required to process the transform | ||
| 479 | var minimumVersion = 100; | ||
| 480 | |||
| 481 | if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out var targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out var updatedMin)) | ||
| 482 | { | ||
| 483 | minimumVersion = Math.Max(targetMin, updatedMin); | ||
| 484 | } | ||
| 485 | |||
| 486 | var summaryRows = new Dictionary<int, Row>(summaryInfoTable.Rows.Count); | ||
| 487 | |||
| 488 | foreach (var row in summaryInfoTable.Rows) | ||
| 489 | { | ||
| 490 | var id = row.FieldAsInteger(0); | ||
| 491 | |||
| 492 | summaryRows[id] = row; | ||
| 493 | |||
| 494 | if ((int)SummaryInformation.Transform.CodePage == id) | ||
| 495 | { | ||
| 496 | row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; | ||
| 497 | row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; | ||
| 498 | } | ||
| 499 | else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == id) | ||
| 500 | { | ||
| 501 | row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; | ||
| 502 | } | ||
| 503 | else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == id) | ||
| 504 | { | ||
| 505 | row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; | ||
| 506 | } | ||
| 507 | else if ((int)SummaryInformation.Transform.ProductCodes == id) | ||
| 508 | { | ||
| 509 | row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); | ||
| 510 | } | ||
| 511 | else if ((int)SummaryInformation.Transform.InstallerRequirement == id) | ||
| 512 | { | ||
| 513 | row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); | ||
| 514 | } | ||
| 515 | else if ((int)SummaryInformation.Transform.Security == id) | ||
| 516 | { | ||
| 517 | row[1] = "4"; | ||
| 518 | } | ||
| 519 | } | ||
| 520 | |||
| 521 | if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) | ||
| 522 | { | ||
| 523 | var summaryRow = summaryInfoTable.CreateRow(null); | ||
| 524 | summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; | ||
| 525 | summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; | ||
| 526 | } | ||
| 527 | |||
| 528 | if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) | ||
| 529 | { | ||
| 530 | var summaryRow = summaryInfoTable.CreateRow(null); | ||
| 531 | summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; | ||
| 532 | summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; | ||
| 533 | } | ||
| 534 | |||
| 535 | if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) | ||
| 536 | { | ||
| 537 | var summaryRow = summaryInfoTable.CreateRow(null); | ||
| 538 | summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; | ||
| 539 | summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); | ||
| 540 | } | ||
| 541 | |||
| 542 | if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.InstallerRequirement)) | ||
| 543 | { | ||
| 544 | var summaryRow = summaryInfoTable.CreateRow(null); | ||
| 545 | summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; | ||
| 546 | summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); | ||
| 547 | } | ||
| 548 | |||
| 549 | if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) | ||
| 550 | { | ||
| 551 | var summaryRow = summaryInfoTable.CreateRow(null); | ||
| 552 | summaryRow[0] = (int)SummaryInformation.Transform.Security; | ||
| 553 | summaryRow[1] = "4"; | ||
| 554 | } | ||
| 555 | } | ||
| 556 | |||
| 557 | private class SummaryInformationStreams | ||
| 558 | { | ||
| 559 | public string TargetSummaryInfoCodepage { get; set; } | ||
| 560 | |||
| 561 | public string TargetPlatformAndLanguage { get; set; } | ||
| 562 | |||
| 563 | public string TargetProductCode { get; set; } | ||
| 564 | |||
| 565 | public string TargetProductVersion { get; set; } | ||
| 566 | |||
| 567 | public string TargetUpgradeCode { get; set; } | ||
| 568 | |||
| 569 | public string TargetMinimumVersion { get; set; } | ||
| 570 | |||
| 571 | public string UpdatedSummaryInfoCodepage { get; set; } | ||
| 572 | |||
| 573 | public string UpdatedPlatformAndLanguage { get; set; } | ||
| 574 | |||
| 575 | public string UpdatedProductCode { get; set; } | ||
| 576 | |||
| 577 | public string UpdatedProductVersion { get; set; } | ||
| 578 | |||
| 579 | public string UpdatedMinimumVersion { get; set; } | ||
| 580 | } | ||
| 581 | } | ||
| 582 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs new file mode 100644 index 00000000..949d5e18 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs | |||
| @@ -0,0 +1,157 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class GetFileFacadesCommand | ||
| 15 | { | ||
| 16 | public GetFileFacadesCommand(IntermediateSection section, IWindowsInstallerBackendHelper backendHelper) | ||
| 17 | { | ||
| 18 | this.Section = section; | ||
| 19 | this.BackendHelper = backendHelper; | ||
| 20 | } | ||
| 21 | |||
| 22 | private IntermediateSection Section { get; } | ||
| 23 | |||
| 24 | private IWindowsInstallerBackendHelper BackendHelper { get; } | ||
| 25 | |||
| 26 | public List<IFileFacade> FileFacades { get; private set; } | ||
| 27 | |||
| 28 | public void Execute() | ||
| 29 | { | ||
| 30 | var facades = new List<IFileFacade>(); | ||
| 31 | |||
| 32 | var assemblyFile = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id); | ||
| 33 | #if TODO_PATCHING_DELTA | ||
| 34 | //var deltaPatchFiles = this.Section.Symbols.OfType<WixDeltaPatchFileSymbol>().ToDictionary(t => t.Id.Id); | ||
| 35 | #endif | ||
| 36 | |||
| 37 | foreach (var file in this.Section.Symbols.OfType<FileSymbol>()) | ||
| 38 | { | ||
| 39 | assemblyFile.TryGetValue(file.Id.Id, out var assembly); | ||
| 40 | |||
| 41 | #if TODO_PATCHING_DELTA | ||
| 42 | //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile); | ||
| 43 | // TODO: should we be passing along delta information to the file facade? Probably, right? | ||
| 44 | #endif | ||
| 45 | var fileFacade = this.BackendHelper.CreateFileFacade(file, assembly); | ||
| 46 | |||
| 47 | facades.Add(fileFacade); | ||
| 48 | } | ||
| 49 | |||
| 50 | #if TODO_PATCHING_DELTA | ||
| 51 | this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); | ||
| 52 | #endif | ||
| 53 | |||
| 54 | this.FileFacades = facades; | ||
| 55 | } | ||
| 56 | |||
| 57 | #if TODO_PATCHING_DELTA | ||
| 58 | /// <summary> | ||
| 59 | /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. | ||
| 60 | /// </summary> | ||
| 61 | public void ResolveDeltaPatchSymbolPaths(Dictionary<string, WixDeltaPatchFileSymbol> deltaPatchFiles, IEnumerable<FileFacade> facades) | ||
| 62 | { | ||
| 63 | ILookup<string, FileFacade> filesByComponent = null; | ||
| 64 | ILookup<string, FileFacade> filesByDirectory = null; | ||
| 65 | ILookup<string, FileFacade> filesByDiskId = null; | ||
| 66 | |||
| 67 | foreach (var row in this.Section.Symbols.OfType<WixDeltaPatchSymbolPathsSymbol>().OrderBy(r => r.SymbolType)) | ||
| 68 | { | ||
| 69 | switch (row.SymbolType) | ||
| 70 | { | ||
| 71 | case SymbolPathType.File: | ||
| 72 | this.MergeSymbolPaths(row, deltaPatchFiles[row.SymbolId]); | ||
| 73 | break; | ||
| 74 | |||
| 75 | case SymbolPathType.Component: | ||
| 76 | if (null == filesByComponent) | ||
| 77 | { | ||
| 78 | filesByComponent = facades.ToLookup(f => f.File.ComponentRef); | ||
| 79 | } | ||
| 80 | |||
| 81 | foreach (var facade in filesByComponent[row.SymbolId]) | ||
| 82 | { | ||
| 83 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); | ||
| 84 | } | ||
| 85 | break; | ||
| 86 | |||
| 87 | case SymbolPathType.Directory: | ||
| 88 | if (null == filesByDirectory) | ||
| 89 | { | ||
| 90 | filesByDirectory = facades.ToLookup(f => f.File.DirectoryRef); | ||
| 91 | } | ||
| 92 | |||
| 93 | foreach (var facade in filesByDirectory[row.SymbolId]) | ||
| 94 | { | ||
| 95 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); | ||
| 96 | } | ||
| 97 | break; | ||
| 98 | |||
| 99 | case SymbolPathType.Media: | ||
| 100 | if (null == filesByDiskId) | ||
| 101 | { | ||
| 102 | filesByDiskId = facades.ToLookup(f => f.File.DiskId.ToString(CultureInfo.InvariantCulture)); | ||
| 103 | } | ||
| 104 | |||
| 105 | foreach (var facade in filesByDiskId[row.SymbolId]) | ||
| 106 | { | ||
| 107 | this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); | ||
| 108 | } | ||
| 109 | break; | ||
| 110 | |||
| 111 | case SymbolPathType.Product: | ||
| 112 | foreach (var fileRow in deltaPatchFiles.Values) | ||
| 113 | { | ||
| 114 | this.MergeSymbolPaths(row, fileRow); | ||
| 115 | } | ||
| 116 | break; | ||
| 117 | |||
| 118 | default: | ||
| 119 | // error | ||
| 120 | break; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | /// <summary> | ||
| 126 | /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row. | ||
| 127 | /// </summary> | ||
| 128 | /// <param name="row">Row from the WixPatchSymbolsPaths table.</param> | ||
| 129 | /// <param name="file">FileRow into which to set symbol information.</param> | ||
| 130 | /// <comment>This includes PreviousData as well.</comment> | ||
| 131 | private void MergeSymbolPaths(WixDeltaPatchSymbolPathsSymbol row, WixDeltaPatchFileSymbol file) | ||
| 132 | { | ||
| 133 | if (file.SymbolPaths is null) | ||
| 134 | { | ||
| 135 | file.SymbolPaths = row.SymbolPaths; | ||
| 136 | } | ||
| 137 | else | ||
| 138 | { | ||
| 139 | file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths); | ||
| 140 | } | ||
| 141 | |||
| 142 | Field field = row.Fields[2]; | ||
| 143 | if (null != field.PreviousData) | ||
| 144 | { | ||
| 145 | if (null == file.PreviousSymbols) | ||
| 146 | { | ||
| 147 | file.PreviousSymbols = field.PreviousData; | ||
| 148 | } | ||
| 149 | else | ||
| 150 | { | ||
| 151 | file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | #endif | ||
| 156 | } | ||
| 157 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs new file mode 100644 index 00000000..2ac563ac --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs | |||
| @@ -0,0 +1,174 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.WindowsInstaller; | ||
| 10 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class GetFileFacadesFromTransforms | ||
| 15 | { | ||
| 16 | public GetFileFacadesFromTransforms(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, FileSystemManager fileSystemManager, IEnumerable<SubStorage> subStorages) | ||
| 17 | { | ||
| 18 | this.Messaging = messaging; | ||
| 19 | this.BackendHelper = backendHelper; | ||
| 20 | this.FileSystemManager = fileSystemManager; | ||
| 21 | this.SubStorages = subStorages; | ||
| 22 | } | ||
| 23 | |||
| 24 | private IMessaging Messaging { get; } | ||
| 25 | |||
| 26 | private IWindowsInstallerBackendHelper BackendHelper { get; } | ||
| 27 | |||
| 28 | private FileSystemManager FileSystemManager { get; } | ||
| 29 | |||
| 30 | private IEnumerable<SubStorage> SubStorages { get; } | ||
| 31 | |||
| 32 | public List<IFileFacade> FileFacades { get; private set; } | ||
| 33 | |||
| 34 | public void Execute() | ||
| 35 | { | ||
| 36 | var allFileRows = new List<IFileFacade>(); | ||
| 37 | |||
| 38 | var patchMediaFileRows = new Dictionary<int, RowDictionary<FileRow>>(); | ||
| 39 | |||
| 40 | //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); | ||
| 41 | |||
| 42 | // Index paired transforms by name without their "#" prefix. | ||
| 43 | var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); | ||
| 44 | |||
| 45 | // Enumerate through main transforms. | ||
| 46 | foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) | ||
| 47 | { | ||
| 48 | var mainTransform = substorage.Data; | ||
| 49 | var mainFileTable = mainTransform.Tables["File"]; | ||
| 50 | |||
| 51 | if (null == mainFileTable) | ||
| 52 | { | ||
| 53 | continue; | ||
| 54 | } | ||
| 55 | |||
| 56 | // Index File table of pairedTransform | ||
| 57 | var pairedTransform = pairedTransforms["#" + substorage.Name]; | ||
| 58 | var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]); | ||
| 59 | |||
| 60 | foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete)) | ||
| 61 | { | ||
| 62 | var mainFileId = mainFileRow.File; | ||
| 63 | |||
| 64 | // We need compare the underlying files and include all file changes. | ||
| 65 | var objectField = (ObjectField)mainFileRow.Fields[9]; | ||
| 66 | var pairedFileRow = pairedFileRows.Get(mainFileId); | ||
| 67 | |||
| 68 | // If the file is new, we always need to add it to the patch. | ||
| 69 | if (mainFileRow.Operation == RowOperation.Add) | ||
| 70 | { | ||
| 71 | if (null != pairedFileRow) // RowOperation.Add | ||
| 72 | { | ||
| 73 | // Always patch-added, but never non-compressed. | ||
| 74 | pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; | ||
| 75 | pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; | ||
| 76 | pairedFileRow.Fields[6].Modified = true; | ||
| 77 | pairedFileRow.Operation = RowOperation.Add; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | else | ||
| 81 | { | ||
| 82 | // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. | ||
| 83 | if (null == objectField.PreviousData) | ||
| 84 | { | ||
| 85 | if (mainFileRow.Operation == RowOperation.None) | ||
| 86 | { | ||
| 87 | continue; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | else | ||
| 91 | { | ||
| 92 | // TODO: should this entire condition be placed in the binder file manager? | ||
| 93 | if (/*(0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&*/ | ||
| 94 | !this.FileSystemManager.CompareFiles(objectField.PreviousData, objectField.Data.ToString())) | ||
| 95 | { | ||
| 96 | // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. | ||
| 97 | mainFileRow.Operation = RowOperation.Modify; | ||
| 98 | if (null != pairedFileRow) | ||
| 99 | { | ||
| 100 | // Always patch-added, but never non-compressed. | ||
| 101 | pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; | ||
| 102 | pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; | ||
| 103 | pairedFileRow.Fields[6].Modified = true; | ||
| 104 | pairedFileRow.Operation = RowOperation.Modify; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | else | ||
| 108 | { | ||
| 109 | // The File is same. We need mark all the attributes as unchanged. | ||
| 110 | mainFileRow.Operation = RowOperation.None; | ||
| 111 | foreach (var field in mainFileRow.Fields) | ||
| 112 | { | ||
| 113 | field.Modified = false; | ||
| 114 | } | ||
| 115 | |||
| 116 | if (null != pairedFileRow) | ||
| 117 | { | ||
| 118 | pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded; | ||
| 119 | pairedFileRow.Fields[6].Modified = false; | ||
| 120 | pairedFileRow.Operation = RowOperation.None; | ||
| 121 | } | ||
| 122 | continue; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | // index patch files by diskId+fileId | ||
| 128 | var diskId = mainFileRow.DiskId; | ||
| 129 | |||
| 130 | if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) | ||
| 131 | { | ||
| 132 | mediaFileRows = new RowDictionary<FileRow>(); | ||
| 133 | patchMediaFileRows.Add(diskId, mediaFileRows); | ||
| 134 | } | ||
| 135 | |||
| 136 | var patchFileRow = mediaFileRows.Get(mainFileId); | ||
| 137 | |||
| 138 | if (null == patchFileRow) | ||
| 139 | { | ||
| 140 | //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 141 | patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 142 | mainFileRow.CopyTo(patchFileRow); | ||
| 143 | |||
| 144 | mediaFileRows.Add(patchFileRow); | ||
| 145 | |||
| 146 | #if TODO_PATCHING_DELTA | ||
| 147 | // TODO: should we be passing along delta information to the file facade? Probably, right? | ||
| 148 | #endif | ||
| 149 | var fileFacade = this.BackendHelper.CreateFileFacade(patchFileRow); | ||
| 150 | |||
| 151 | allFileRows.Add(fileFacade); | ||
| 152 | } | ||
| 153 | else | ||
| 154 | { | ||
| 155 | // TODO: confirm the rest of data is identical? | ||
| 156 | |||
| 157 | // make sure Source is same. Otherwise we are silently ignoring a file. | ||
| 158 | if (0 != String.Compare(patchFileRow.Source, mainFileRow.Source, StringComparison.OrdinalIgnoreCase)) | ||
| 159 | { | ||
| 160 | this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, mainFileId, patchFileRow.Source, mainFileRow.Source)); | ||
| 161 | } | ||
| 162 | |||
| 163 | #if TODO_PATCHING_DELTA | ||
| 164 | // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. | ||
| 165 | patchFileRow.AppendPreviousDataFrom(mainFileRow); | ||
| 166 | #endif | ||
| 167 | } | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | this.FileFacades = allFileRows; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs new file mode 100644 index 00000000..2eb95bc5 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs | |||
| @@ -0,0 +1,215 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Data.WindowsInstaller; | ||
| 12 | using WixToolset.Extensibility; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | internal class LoadTableDefinitionsCommand | ||
| 16 | { | ||
| 17 | public LoadTableDefinitionsCommand(IMessaging messaging, IntermediateSection section, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions) | ||
| 18 | { | ||
| 19 | this.Messaging = messaging; | ||
| 20 | this.Section = section; | ||
| 21 | this.BackendExtensions = backendExtensions; | ||
| 22 | } | ||
| 23 | |||
| 24 | public IMessaging Messaging { get; } | ||
| 25 | |||
| 26 | private IntermediateSection Section { get; } | ||
| 27 | |||
| 28 | private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; } | ||
| 29 | |||
| 30 | public TableDefinitionCollection TableDefinitions { get; private set; } | ||
| 31 | |||
| 32 | public TableDefinitionCollection Execute() | ||
| 33 | { | ||
| 34 | var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); | ||
| 35 | var customColumnsById = this.Section.Symbols.OfType<WixCustomTableColumnSymbol>().ToDictionary(t => t.Id.Id); | ||
| 36 | |||
| 37 | if (customColumnsById.Any()) | ||
| 38 | { | ||
| 39 | foreach (var symbol in this.Section.Symbols.OfType<WixCustomTableSymbol>()) | ||
| 40 | { | ||
| 41 | var customTableDefinition = this.CreateCustomTable(symbol, customColumnsById); | ||
| 42 | tableDefinitions.Add(customTableDefinition); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | foreach (var backendExtension in this.BackendExtensions) | ||
| 47 | { | ||
| 48 | foreach (var tableDefinition in backendExtension.TableDefinitions) | ||
| 49 | { | ||
| 50 | if (tableDefinitions.Contains(tableDefinition.Name)) | ||
| 51 | { | ||
| 52 | this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(backendExtension.GetType().Assembly.Location, tableDefinition.Name)); | ||
| 53 | } | ||
| 54 | |||
| 55 | tableDefinitions.Add(tableDefinition); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | this.TableDefinitions = tableDefinitions; | ||
| 60 | return this.TableDefinitions; | ||
| 61 | } | ||
| 62 | |||
| 63 | private TableDefinition CreateCustomTable(WixCustomTableSymbol symbol, Dictionary<string, WixCustomTableColumnSymbol> customColumnsById) | ||
| 64 | { | ||
| 65 | var columnNames = symbol.ColumnNamesSeparated; | ||
| 66 | var columns = new List<ColumnDefinition>(columnNames.Length); | ||
| 67 | |||
| 68 | foreach (var name in columnNames) | ||
| 69 | { | ||
| 70 | var column = customColumnsById[symbol.Id.Id + "/" + name]; | ||
| 71 | |||
| 72 | var type = ColumnType.Unknown; | ||
| 73 | |||
| 74 | if (column.Type == IntermediateFieldType.String) | ||
| 75 | { | ||
| 76 | type = column.Localizable ? ColumnType.Localized : ColumnType.String; | ||
| 77 | } | ||
| 78 | else if (column.Type == IntermediateFieldType.Number) | ||
| 79 | { | ||
| 80 | type = ColumnType.Number; | ||
| 81 | } | ||
| 82 | else if (column.Type == IntermediateFieldType.Path) | ||
| 83 | { | ||
| 84 | type = ColumnType.Object; | ||
| 85 | } | ||
| 86 | |||
| 87 | var category = ColumnCategory.Unknown; | ||
| 88 | switch (column.Category) | ||
| 89 | { | ||
| 90 | case WixCustomTableColumnCategoryType.Text: | ||
| 91 | category = ColumnCategory.Text; | ||
| 92 | break; | ||
| 93 | case WixCustomTableColumnCategoryType.UpperCase: | ||
| 94 | category = ColumnCategory.UpperCase; | ||
| 95 | break; | ||
| 96 | case WixCustomTableColumnCategoryType.LowerCase: | ||
| 97 | category = ColumnCategory.LowerCase; | ||
| 98 | break; | ||
| 99 | case WixCustomTableColumnCategoryType.Integer: | ||
| 100 | category = ColumnCategory.Integer; | ||
| 101 | break; | ||
| 102 | case WixCustomTableColumnCategoryType.DoubleInteger: | ||
| 103 | category = ColumnCategory.DoubleInteger; | ||
| 104 | break; | ||
| 105 | case WixCustomTableColumnCategoryType.TimeDate: | ||
| 106 | category = ColumnCategory.TimeDate; | ||
| 107 | break; | ||
| 108 | case WixCustomTableColumnCategoryType.Identifier: | ||
| 109 | category = ColumnCategory.Identifier; | ||
| 110 | break; | ||
| 111 | case WixCustomTableColumnCategoryType.Property: | ||
| 112 | category = ColumnCategory.Property; | ||
| 113 | break; | ||
| 114 | case WixCustomTableColumnCategoryType.Filename: | ||
| 115 | category = ColumnCategory.Filename; | ||
| 116 | break; | ||
| 117 | case WixCustomTableColumnCategoryType.WildCardFilename: | ||
| 118 | category = ColumnCategory.WildCardFilename; | ||
| 119 | break; | ||
| 120 | case WixCustomTableColumnCategoryType.Path: | ||
| 121 | category = ColumnCategory.Path; | ||
| 122 | break; | ||
| 123 | case WixCustomTableColumnCategoryType.Paths: | ||
| 124 | category = ColumnCategory.Paths; | ||
| 125 | break; | ||
| 126 | case WixCustomTableColumnCategoryType.AnyPath: | ||
| 127 | category = ColumnCategory.AnyPath; | ||
| 128 | break; | ||
| 129 | case WixCustomTableColumnCategoryType.DefaultDir: | ||
| 130 | category = ColumnCategory.DefaultDir; | ||
| 131 | break; | ||
| 132 | case WixCustomTableColumnCategoryType.RegPath: | ||
| 133 | category = ColumnCategory.RegPath; | ||
| 134 | break; | ||
| 135 | case WixCustomTableColumnCategoryType.Formatted: | ||
| 136 | category = ColumnCategory.Formatted; | ||
| 137 | break; | ||
| 138 | case WixCustomTableColumnCategoryType.FormattedSddl: | ||
| 139 | category = ColumnCategory.FormattedSDDLText; | ||
| 140 | break; | ||
| 141 | case WixCustomTableColumnCategoryType.Template: | ||
| 142 | category = ColumnCategory.Template; | ||
| 143 | break; | ||
| 144 | case WixCustomTableColumnCategoryType.Condition: | ||
| 145 | category = ColumnCategory.Condition; | ||
| 146 | break; | ||
| 147 | case WixCustomTableColumnCategoryType.Guid: | ||
| 148 | category = ColumnCategory.Guid; | ||
| 149 | break; | ||
| 150 | case WixCustomTableColumnCategoryType.Version: | ||
| 151 | category = ColumnCategory.Version; | ||
| 152 | break; | ||
| 153 | case WixCustomTableColumnCategoryType.Language: | ||
| 154 | category = ColumnCategory.Language; | ||
| 155 | break; | ||
| 156 | case WixCustomTableColumnCategoryType.Binary: | ||
| 157 | category = ColumnCategory.Binary; | ||
| 158 | break; | ||
| 159 | case WixCustomTableColumnCategoryType.CustomSource: | ||
| 160 | category = ColumnCategory.CustomSource; | ||
| 161 | break; | ||
| 162 | case WixCustomTableColumnCategoryType.Cabinet: | ||
| 163 | category = ColumnCategory.Cabinet; | ||
| 164 | break; | ||
| 165 | case WixCustomTableColumnCategoryType.Shortcut: | ||
| 166 | category = ColumnCategory.Shortcut; | ||
| 167 | break; | ||
| 168 | case null: | ||
| 169 | default: | ||
| 170 | break; | ||
| 171 | } | ||
| 172 | |||
| 173 | var modularization = ColumnModularizeType.None; | ||
| 174 | |||
| 175 | switch (column.Modularize) | ||
| 176 | { | ||
| 177 | case null: | ||
| 178 | case WixCustomTableColumnModularizeType.None: | ||
| 179 | modularization = ColumnModularizeType.None; | ||
| 180 | break; | ||
| 181 | case WixCustomTableColumnModularizeType.Column: | ||
| 182 | modularization = ColumnModularizeType.Column; | ||
| 183 | break; | ||
| 184 | case WixCustomTableColumnModularizeType.CompanionFile: | ||
| 185 | modularization = ColumnModularizeType.CompanionFile; | ||
| 186 | break; | ||
| 187 | case WixCustomTableColumnModularizeType.Condition: | ||
| 188 | modularization = ColumnModularizeType.Condition; | ||
| 189 | break; | ||
| 190 | case WixCustomTableColumnModularizeType.ControlEventArgument: | ||
| 191 | modularization = ColumnModularizeType.ControlEventArgument; | ||
| 192 | break; | ||
| 193 | case WixCustomTableColumnModularizeType.ControlText: | ||
| 194 | modularization = ColumnModularizeType.ControlText; | ||
| 195 | break; | ||
| 196 | case WixCustomTableColumnModularizeType.Icon: | ||
| 197 | modularization = ColumnModularizeType.Icon; | ||
| 198 | break; | ||
| 199 | case WixCustomTableColumnModularizeType.Property: | ||
| 200 | modularization = ColumnModularizeType.Property; | ||
| 201 | break; | ||
| 202 | case WixCustomTableColumnModularizeType.SemicolonDelimited: | ||
| 203 | modularization = ColumnModularizeType.SemicolonDelimited; | ||
| 204 | break; | ||
| 205 | } | ||
| 206 | |||
| 207 | var columnDefinition = new ColumnDefinition(name, type, column.Width, column.PrimaryKey, column.Nullable, category, column.MinValue, column.MaxValue, column.KeyTable, column.KeyColumn, column.Set, column.Description, modularization, ColumnType.Localized == type, useCData: true, column.Unreal); | ||
| 208 | columns.Add(columnDefinition); | ||
| 209 | } | ||
| 210 | |||
| 211 | var customTable = new TableDefinition(symbol.Id.Id, null, columns, symbol.Unreal); | ||
| 212 | return customTable; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs new file mode 100644 index 00000000..6446692e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs | |||
| @@ -0,0 +1,331 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Runtime.InteropServices; | ||
| 11 | using System.Text; | ||
| 12 | using WixToolset.Core.Native.Msi; | ||
| 13 | using WixToolset.Core.Native.Msm; | ||
| 14 | using WixToolset.Data; | ||
| 15 | using WixToolset.Data.Symbols; | ||
| 16 | using WixToolset.Data.WindowsInstaller; | ||
| 17 | using WixToolset.Extensibility.Data; | ||
| 18 | using WixToolset.Extensibility.Services; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Merge modules into the database at output path. | ||
| 22 | /// </summary> | ||
| 23 | internal class MergeModulesCommand | ||
| 24 | { | ||
| 25 | public MergeModulesCommand(IMessaging messaging, IEnumerable<IFileFacade> fileFacades, IntermediateSection section, IEnumerable<string> suppressedTableNames, string outputPath, string intermediateFolder) | ||
| 26 | { | ||
| 27 | this.Messaging = messaging; | ||
| 28 | this.FileFacades = fileFacades; | ||
| 29 | this.Section = section; | ||
| 30 | this.SuppressedTableNames = suppressedTableNames ?? Array.Empty<string>(); | ||
| 31 | this.OutputPath = outputPath; | ||
| 32 | this.IntermediateFolder = intermediateFolder; | ||
| 33 | } | ||
| 34 | |||
| 35 | private IMessaging Messaging { get; } | ||
| 36 | |||
| 37 | private IEnumerable<IFileFacade> FileFacades { get; } | ||
| 38 | |||
| 39 | private IntermediateSection Section { get; } | ||
| 40 | |||
| 41 | private IEnumerable<string> SuppressedTableNames { get; } | ||
| 42 | |||
| 43 | private string OutputPath { get; } | ||
| 44 | |||
| 45 | private string IntermediateFolder { get; } | ||
| 46 | |||
| 47 | public void Execute() | ||
| 48 | { | ||
| 49 | var wixMergeSymbols = this.Section.Symbols.OfType<WixMergeSymbol>().ToList(); | ||
| 50 | if (!wixMergeSymbols.Any()) | ||
| 51 | { | ||
| 52 | return; | ||
| 53 | } | ||
| 54 | |||
| 55 | IMsmMerge2 merge = null; | ||
| 56 | var commit = true; | ||
| 57 | var logOpen = false; | ||
| 58 | var databaseOpen = false; | ||
| 59 | var logPath = Path.Combine(this.IntermediateFolder, "merge.log"); | ||
| 60 | |||
| 61 | try | ||
| 62 | { | ||
| 63 | merge = MsmInterop.GetMsmMerge(); | ||
| 64 | |||
| 65 | merge.OpenLog(logPath); | ||
| 66 | logOpen = true; | ||
| 67 | |||
| 68 | merge.OpenDatabase(this.OutputPath); | ||
| 69 | databaseOpen = true; | ||
| 70 | |||
| 71 | var featureModulesByMergeId = this.Section.Symbols.OfType<WixFeatureModulesSymbol>().GroupBy(t => t.WixMergeRef).ToDictionary(g => g.Key); | ||
| 72 | |||
| 73 | // process all the merge rows | ||
| 74 | foreach (var wixMergeRow in wixMergeSymbols) | ||
| 75 | { | ||
| 76 | var moduleOpen = false; | ||
| 77 | |||
| 78 | try | ||
| 79 | { | ||
| 80 | short mergeLanguage; | ||
| 81 | |||
| 82 | try | ||
| 83 | { | ||
| 84 | mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); | ||
| 85 | } | ||
| 86 | catch (FormatException) | ||
| 87 | { | ||
| 88 | this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.Language.ToString())); | ||
| 89 | continue; | ||
| 90 | } | ||
| 91 | |||
| 92 | this.Messaging.Write(VerboseMessages.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); | ||
| 93 | merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); | ||
| 94 | moduleOpen = true; | ||
| 95 | |||
| 96 | // If there is merge configuration data, create a callback object to contain it all. | ||
| 97 | ConfigurationCallback callback = null; | ||
| 98 | if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData)) | ||
| 99 | { | ||
| 100 | callback = new ConfigurationCallback(wixMergeRow.ConfigurationData); | ||
| 101 | } | ||
| 102 | |||
| 103 | // Merge the module into the database that's being built. | ||
| 104 | this.Messaging.Write(VerboseMessages.MergingMergeModule(wixMergeRow.SourceFile)); | ||
| 105 | merge.MergeEx(wixMergeRow.FeatureRef, wixMergeRow.DirectoryRef, callback); | ||
| 106 | |||
| 107 | // Connect any non-primary features. | ||
| 108 | if (featureModulesByMergeId.TryGetValue(wixMergeRow.Id.Id, out var featureModules)) | ||
| 109 | { | ||
| 110 | foreach (var featureModule in featureModules) | ||
| 111 | { | ||
| 112 | this.Messaging.Write(VerboseMessages.ConnectingMergeModule(wixMergeRow.SourceFile, featureModule.FeatureRef)); | ||
| 113 | merge.Connect(featureModule.FeatureRef); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | catch (COMException) | ||
| 118 | { | ||
| 119 | commit = false; | ||
| 120 | } | ||
| 121 | finally | ||
| 122 | { | ||
| 123 | var mergeErrors = merge.Errors; | ||
| 124 | |||
| 125 | // display all the errors encountered during the merge operations for this module | ||
| 126 | for (var i = 1; i <= mergeErrors.Count; i++) | ||
| 127 | { | ||
| 128 | var mergeError = mergeErrors[i]; | ||
| 129 | var databaseKeys = new StringBuilder(); | ||
| 130 | var moduleKeys = new StringBuilder(); | ||
| 131 | |||
| 132 | // build a string of the database keys | ||
| 133 | for (var j = 1; j <= mergeError.DatabaseKeys.Count; j++) | ||
| 134 | { | ||
| 135 | if (1 != j) | ||
| 136 | { | ||
| 137 | databaseKeys.Append(';'); | ||
| 138 | } | ||
| 139 | databaseKeys.Append(mergeError.DatabaseKeys[j]); | ||
| 140 | } | ||
| 141 | |||
| 142 | // build a string of the module keys | ||
| 143 | for (var j = 1; j <= mergeError.ModuleKeys.Count; j++) | ||
| 144 | { | ||
| 145 | if (1 != j) | ||
| 146 | { | ||
| 147 | moduleKeys.Append(';'); | ||
| 148 | } | ||
| 149 | moduleKeys.Append(mergeError.ModuleKeys[j]); | ||
| 150 | } | ||
| 151 | |||
| 152 | // display the merge error based on the msm error type | ||
| 153 | switch (mergeError.Type) | ||
| 154 | { | ||
| 155 | case MsmErrorType.msmErrorExclusion: | ||
| 156 | this.Messaging.Write(ErrorMessages.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleKeys.ToString())); | ||
| 157 | break; | ||
| 158 | case MsmErrorType.msmErrorFeatureRequired: | ||
| 159 | this.Messaging.Write(ErrorMessages.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id.Id)); | ||
| 160 | break; | ||
| 161 | case MsmErrorType.msmErrorLanguageFailed: | ||
| 162 | this.Messaging.Write(ErrorMessages.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); | ||
| 163 | break; | ||
| 164 | case MsmErrorType.msmErrorLanguageUnsupported: | ||
| 165 | this.Messaging.Write(ErrorMessages.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); | ||
| 166 | break; | ||
| 167 | case MsmErrorType.msmErrorResequenceMerge: | ||
| 168 | this.Messaging.Write(WarningMessages.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); | ||
| 169 | break; | ||
| 170 | case MsmErrorType.msmErrorTableMerge: | ||
| 171 | if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table | ||
| 172 | { | ||
| 173 | this.Messaging.Write(WarningMessages.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); | ||
| 174 | } | ||
| 175 | break; | ||
| 176 | case MsmErrorType.msmErrorPlatformMismatch: | ||
| 177 | this.Messaging.Write(ErrorMessages.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); | ||
| 178 | break; | ||
| 179 | default: | ||
| 180 | this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "Encountered an unexpected merge error of type '{0}' for which there is currently no error message to display. More information about the merge and the failure can be found in the merge log: '{1}'", Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); | ||
| 181 | break; | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | if (0 >= mergeErrors.Count && !commit) | ||
| 186 | { | ||
| 187 | this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'", wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace)); | ||
| 188 | } | ||
| 189 | |||
| 190 | if (moduleOpen) | ||
| 191 | { | ||
| 192 | merge.CloseModule(); | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
| 197 | finally | ||
| 198 | { | ||
| 199 | if (databaseOpen) | ||
| 200 | { | ||
| 201 | merge.CloseDatabase(commit); | ||
| 202 | } | ||
| 203 | |||
| 204 | if (logOpen) | ||
| 205 | { | ||
| 206 | merge.CloseLog(); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | // stop processing if an error previously occurred | ||
| 211 | if (this.Messaging.EncounteredError) | ||
| 212 | { | ||
| 213 | return; | ||
| 214 | } | ||
| 215 | |||
| 216 | using (var db = new Database(this.OutputPath, OpenDatabase.Direct)) | ||
| 217 | { | ||
| 218 | // Suppress individual actions. | ||
| 219 | foreach (var suppressAction in this.Section.Symbols.OfType<WixSuppressActionSymbol>()) | ||
| 220 | { | ||
| 221 | var tableName = suppressAction.SequenceTable.WindowsInstallerTableName(); | ||
| 222 | if (db.TableExists(tableName)) | ||
| 223 | { | ||
| 224 | var query = $"SELECT * FROM {tableName} WHERE `Action` = '{suppressAction.Action}'"; | ||
| 225 | |||
| 226 | using (var view = db.OpenExecuteView(query)) | ||
| 227 | using (var record = view.Fetch()) | ||
| 228 | { | ||
| 229 | if (null != record) | ||
| 230 | { | ||
| 231 | this.Messaging.Write(WarningMessages.SuppressMergedAction(suppressAction.Action, tableName)); | ||
| 232 | view.Modify(ModifyView.Delete, record); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | // Query for merge module actions in suppressed sequences and drop them. | ||
| 239 | foreach (var tableName in this.SuppressedTableNames) | ||
| 240 | { | ||
| 241 | if (!db.TableExists(tableName)) | ||
| 242 | { | ||
| 243 | continue; | ||
| 244 | } | ||
| 245 | |||
| 246 | using (var view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) | ||
| 247 | { | ||
| 248 | foreach (var resultRecord in view.Records) | ||
| 249 | { | ||
| 250 | this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName)); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | // drop suppressed sequences | ||
| 255 | using (var view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) | ||
| 256 | { | ||
| 257 | } | ||
| 258 | |||
| 259 | // delete the validation rows | ||
| 260 | using (var view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) | ||
| 261 | using (var record = new Record(1)) | ||
| 262 | { | ||
| 263 | record.SetString(1, tableName); | ||
| 264 | view.Execute(record); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | // now update the Attributes column for the files from the Merge Modules | ||
| 269 | this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles()); | ||
| 270 | using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) | ||
| 271 | { | ||
| 272 | foreach (var file in this.FileFacades) | ||
| 273 | { | ||
| 274 | if (!file.FromModule) | ||
| 275 | { | ||
| 276 | continue; | ||
| 277 | } | ||
| 278 | |||
| 279 | using (var record = new Record(1)) | ||
| 280 | { | ||
| 281 | record.SetString(1, file.Id); | ||
| 282 | view.Execute(record); | ||
| 283 | } | ||
| 284 | |||
| 285 | using (var recordUpdate = view.Fetch()) | ||
| 286 | { | ||
| 287 | if (null == recordUpdate) | ||
| 288 | { | ||
| 289 | throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module."); | ||
| 290 | } | ||
| 291 | |||
| 292 | recordUpdate.SetInteger(1, file.Sequence); | ||
| 293 | |||
| 294 | // Update the file attributes to match the compression specified | ||
| 295 | // on the Merge element or on the Package element. | ||
| 296 | var attributes = 0; | ||
| 297 | |||
| 298 | // Get the current value if its not null. | ||
| 299 | if (!recordUpdate.IsNull(2)) | ||
| 300 | { | ||
| 301 | attributes = recordUpdate.GetInteger(2); | ||
| 302 | } | ||
| 303 | |||
| 304 | if (file.Compressed) | ||
| 305 | { | ||
| 306 | attributes |= WindowsInstallerConstants.MsidbFileAttributesCompressed; | ||
| 307 | attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; | ||
| 308 | } | ||
| 309 | else if (file.Uncompressed) | ||
| 310 | { | ||
| 311 | attributes |= WindowsInstallerConstants.MsidbFileAttributesNoncompressed; | ||
| 312 | attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; | ||
| 313 | } | ||
| 314 | else // clear all compression bits. | ||
| 315 | { | ||
| 316 | attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; | ||
| 317 | attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; | ||
| 318 | } | ||
| 319 | |||
| 320 | recordUpdate.SetInteger(2, attributes); | ||
| 321 | |||
| 322 | view.Modify(ModifyView.Update, recordUpdate); | ||
| 323 | } | ||
| 324 | } | ||
| 325 | } | ||
| 326 | |||
| 327 | db.Commit(); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs new file mode 100644 index 00000000..04f1b771 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs | |||
| @@ -0,0 +1,236 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Text; | ||
| 11 | using System.Text.RegularExpressions; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Data.WindowsInstaller; | ||
| 15 | using WixToolset.Extensibility.Services; | ||
| 16 | |||
| 17 | internal class ModularizeCommand | ||
| 18 | { | ||
| 19 | public ModularizeCommand(IBackendHelper backendHelper, WindowsInstallerData output, string modularizationSuffix, IEnumerable<WixSuppressModularizationSymbol> suppressSymbols) | ||
| 20 | { | ||
| 21 | this.BackendHelper = backendHelper; | ||
| 22 | this.Output = output; | ||
| 23 | this.ModularizationSuffix = modularizationSuffix; | ||
| 24 | |||
| 25 | // Gather all the unique suppress modularization identifiers. | ||
| 26 | this.SuppressModularizationIdentifiers = new HashSet<string>(suppressSymbols.Select(s => s.SuppressIdentifier)); | ||
| 27 | } | ||
| 28 | |||
| 29 | private IBackendHelper BackendHelper { get; } | ||
| 30 | |||
| 31 | private WindowsInstallerData Output { get; } | ||
| 32 | |||
| 33 | private string ModularizationSuffix { get; } | ||
| 34 | |||
| 35 | private HashSet<string> SuppressModularizationIdentifiers { get; } | ||
| 36 | |||
| 37 | public void Execute() | ||
| 38 | { | ||
| 39 | foreach (var table in this.Output.Tables) | ||
| 40 | { | ||
| 41 | this.ModularizeTable(table); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | private void ModularizeTable(Table table) | ||
| 46 | { | ||
| 47 | var modularizedColumns = new List<int>(); | ||
| 48 | |||
| 49 | // find the modularized columns | ||
| 50 | for (var i = 0; i < table.Definition.Columns.Length; ++i) | ||
| 51 | { | ||
| 52 | if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType) | ||
| 53 | { | ||
| 54 | modularizedColumns.Add(i); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | if (0 < modularizedColumns.Count) | ||
| 59 | { | ||
| 60 | foreach (var row in table.Rows) | ||
| 61 | { | ||
| 62 | foreach (var modularizedColumn in modularizedColumns) | ||
| 63 | { | ||
| 64 | var field = row.Fields[modularizedColumn]; | ||
| 65 | |||
| 66 | if (field.Data != null) | ||
| 67 | { | ||
| 68 | field.Data = this.ModularizedRowFieldValue(row, field); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | private string ModularizedRowFieldValue(Row row, Field field) | ||
| 76 | { | ||
| 77 | var fieldData = field.AsString(); | ||
| 78 | |||
| 79 | if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData))) | ||
| 80 | { | ||
| 81 | var modularizeType = field.Column.ModularizeType; | ||
| 82 | |||
| 83 | // special logic for the ControlEvent table's Argument column | ||
| 84 | // this column requires different modularization methods depending upon the value of the Event column | ||
| 85 | if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType) | ||
| 86 | { | ||
| 87 | switch (row[2].ToString()) | ||
| 88 | { | ||
| 89 | case "CheckExistingTargetPath": // redirectable property name | ||
| 90 | case "CheckTargetPath": | ||
| 91 | case "DoAction": // custom action name | ||
| 92 | case "NewDialog": // dialog name | ||
| 93 | case "SelectionBrowse": | ||
| 94 | case "SetTargetPath": | ||
| 95 | case "SpawnDialog": | ||
| 96 | case "SpawnWaitDialog": | ||
| 97 | if (this.BackendHelper.IsValidIdentifier(fieldData)) | ||
| 98 | { | ||
| 99 | modularizeType = ColumnModularizeType.Column; | ||
| 100 | } | ||
| 101 | else | ||
| 102 | { | ||
| 103 | modularizeType = ColumnModularizeType.Property; | ||
| 104 | } | ||
| 105 | break; | ||
| 106 | default: // formatted | ||
| 107 | modularizeType = ColumnModularizeType.Property; | ||
| 108 | break; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | else if (ColumnModularizeType.ControlText == field.Column.ModularizeType) | ||
| 112 | { | ||
| 113 | // icons are stored in the Binary table, so they get column-type modularization | ||
| 114 | if (("Bitmap" == row[2].ToString() || "Icon" == row[2].ToString()) && this.BackendHelper.IsValidIdentifier(fieldData)) | ||
| 115 | { | ||
| 116 | modularizeType = ColumnModularizeType.Column; | ||
| 117 | } | ||
| 118 | else | ||
| 119 | { | ||
| 120 | modularizeType = ColumnModularizeType.Property; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | switch (modularizeType) | ||
| 125 | { | ||
| 126 | case ColumnModularizeType.Column: | ||
| 127 | // ensure the value is an identifier (otherwise it shouldn't be modularized this way) | ||
| 128 | if (!this.BackendHelper.IsValidIdentifier(fieldData)) | ||
| 129 | { | ||
| 130 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData)); | ||
| 131 | } | ||
| 132 | |||
| 133 | // if we're not supposed to suppress modularization of this identifier | ||
| 134 | if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) | ||
| 135 | { | ||
| 136 | fieldData = String.Concat(fieldData, this.ModularizationSuffix); | ||
| 137 | } | ||
| 138 | break; | ||
| 139 | |||
| 140 | case ColumnModularizeType.Property: | ||
| 141 | case ColumnModularizeType.Condition: | ||
| 142 | Regex regex; | ||
| 143 | if (ColumnModularizeType.Property == modularizeType) | ||
| 144 | { | ||
| 145 | regex = new Regex(@"\[(?<identifier>[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture); | ||
| 146 | } | ||
| 147 | else | ||
| 148 | { | ||
| 149 | Debug.Assert(ColumnModularizeType.Condition == modularizeType); | ||
| 150 | |||
| 151 | // This heinous looking regular expression is actually quite an elegant way | ||
| 152 | // to shred the entire condition into the identifiers that need to be | ||
| 153 | // modularized. Let's break it down piece by piece: | ||
| 154 | // | ||
| 155 | // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the | ||
| 156 | // regular expression is case insensitive so we don't have to worry about | ||
| 157 | // all the permutations of these strings. | ||
| 158 | // 2. Look for quoted strings. Quoted strings are just text and are ignored | ||
| 159 | // outright. | ||
| 160 | // 3. Look for environment variables. These look like identifiers we might | ||
| 161 | // otherwise be interested in but start with a percent sign. Like quoted | ||
| 162 | // strings these enviroment variable references are ignored outright. | ||
| 163 | // 4. Match all identifiers that are things that need to be modularized. Note | ||
| 164 | // the special characters (!, $, ?, &) that denote Component and Feature states. | ||
| 165 | regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); | ||
| 166 | |||
| 167 | // less performant version of the above with captures showing where everything lives | ||
| 168 | // regex = new Regex(@"(?<operator>NOT|EQV|XOR|OR|AND|IMP)|(?<string>"".*?"")|(?<environment>%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); | ||
| 169 | } | ||
| 170 | |||
| 171 | var matches = regex.Matches(fieldData); | ||
| 172 | |||
| 173 | var sb = new StringBuilder(fieldData); | ||
| 174 | |||
| 175 | // Notice how this code walks backward through the list | ||
| 176 | // because it modifies the string as we through it. | ||
| 177 | for (var i = matches.Count - 1; 0 <= i; i--) | ||
| 178 | { | ||
| 179 | var group = matches[i].Groups["identifier"]; | ||
| 180 | if (group.Success) | ||
| 181 | { | ||
| 182 | var identifier = group.Value; | ||
| 183 | if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier)) | ||
| 184 | { | ||
| 185 | sb.Insert(group.Index + group.Length, this.ModularizationSuffix); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | fieldData = sb.ToString(); | ||
| 191 | break; | ||
| 192 | |||
| 193 | case ColumnModularizeType.CompanionFile: | ||
| 194 | // if we're not supposed to ignore this identifier and the value does not start with | ||
| 195 | // a digit, we must have a companion file so modularize it | ||
| 196 | if (!this.SuppressModularizationIdentifiers.Contains(fieldData) && | ||
| 197 | 0 < fieldData.Length && !Char.IsDigit(fieldData, 0)) | ||
| 198 | { | ||
| 199 | fieldData = String.Concat(fieldData, this.ModularizationSuffix); | ||
| 200 | } | ||
| 201 | break; | ||
| 202 | |||
| 203 | case ColumnModularizeType.Icon: | ||
| 204 | if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) | ||
| 205 | { | ||
| 206 | var start = fieldData.LastIndexOf(".", StringComparison.Ordinal); | ||
| 207 | if (-1 == start) | ||
| 208 | { | ||
| 209 | fieldData = String.Concat(fieldData, this.ModularizationSuffix); | ||
| 210 | } | ||
| 211 | else | ||
| 212 | { | ||
| 213 | fieldData = String.Concat(fieldData.Substring(0, start), this.ModularizationSuffix, fieldData.Substring(start)); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | break; | ||
| 217 | |||
| 218 | case ColumnModularizeType.SemicolonDelimited: | ||
| 219 | var keys = fieldData.Split(';'); | ||
| 220 | for (var i = 0; i < keys.Length; ++i) | ||
| 221 | { | ||
| 222 | if (!String.IsNullOrEmpty(keys[i])) | ||
| 223 | { | ||
| 224 | keys[i] = String.Concat(keys[i], this.ModularizationSuffix); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | fieldData = String.Join(";", keys); | ||
| 229 | break; | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | return fieldData; | ||
| 234 | } | ||
| 235 | } | ||
| 236 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs new file mode 100644 index 00000000..5dd4d3ea --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs | |||
| @@ -0,0 +1,119 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class OptimizeFileFacadesOrderCommand | ||
| 14 | { | ||
| 15 | public OptimizeFileFacadesOrderCommand(IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform, List<IFileFacade> fileFacades) | ||
| 16 | { | ||
| 17 | this.BackendHelper = helper; | ||
| 18 | this.PathResolver = pathResolver; | ||
| 19 | this.Section = section; | ||
| 20 | this.Platform = platform; | ||
| 21 | this.FileFacades = fileFacades; | ||
| 22 | } | ||
| 23 | |||
| 24 | public List<IFileFacade> FileFacades { get; private set; } | ||
| 25 | |||
| 26 | private IBackendHelper BackendHelper { get; } | ||
| 27 | |||
| 28 | private IPathResolver PathResolver { get; } | ||
| 29 | |||
| 30 | private IntermediateSection Section { get; } | ||
| 31 | |||
| 32 | private Platform Platform { get; } | ||
| 33 | |||
| 34 | public List<IFileFacade> Execute() | ||
| 35 | { | ||
| 36 | var canonicalComponentTargetPaths = this.ComponentTargetPaths(); | ||
| 37 | |||
| 38 | this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths, this.Section.Type == SectionType.Module)); | ||
| 39 | |||
| 40 | return this.FileFacades; | ||
| 41 | } | ||
| 42 | |||
| 43 | private Dictionary<string, string> ComponentTargetPaths() | ||
| 44 | { | ||
| 45 | var directories = this.ResolveDirectories(); | ||
| 46 | |||
| 47 | var canonicalPathsByDirectoryId = new Dictionary<string, string>(); | ||
| 48 | foreach (var component in this.Section.Symbols.OfType<ComponentSymbol>()) | ||
| 49 | { | ||
| 50 | var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(directories, null, component.DirectoryRef, this.Platform); | ||
| 51 | canonicalPathsByDirectoryId.Add(component.Id.Id, directoryPath); | ||
| 52 | } | ||
| 53 | |||
| 54 | return canonicalPathsByDirectoryId; | ||
| 55 | } | ||
| 56 | |||
| 57 | private Dictionary<string, IResolvedDirectory> ResolveDirectories() | ||
| 58 | { | ||
| 59 | var targetPathsByDirectoryId = new Dictionary<string, IResolvedDirectory>(); | ||
| 60 | |||
| 61 | // Get the target paths for all directories. | ||
| 62 | foreach (var directory in this.Section.Symbols.OfType<DirectorySymbol>()) | ||
| 63 | { | ||
| 64 | var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); | ||
| 65 | targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); | ||
| 66 | } | ||
| 67 | |||
| 68 | return targetPathsByDirectoryId; | ||
| 69 | } | ||
| 70 | |||
| 71 | private class FileFacadeOptimizer : IComparer<IFileFacade> | ||
| 72 | { | ||
| 73 | public FileFacadeOptimizer(Dictionary<string, string> componentTargetPaths, bool optimizingMergeModule) | ||
| 74 | { | ||
| 75 | this.ComponentTargetPaths = componentTargetPaths; | ||
| 76 | this.OptimizingMergeModule = optimizingMergeModule; | ||
| 77 | } | ||
| 78 | |||
| 79 | private Dictionary<string, string> ComponentTargetPaths { get; } | ||
| 80 | |||
| 81 | private bool OptimizingMergeModule { get; } | ||
| 82 | |||
| 83 | public int Compare(IFileFacade x, IFileFacade y) | ||
| 84 | { | ||
| 85 | // First group files by DiskId but ignore if processing a Merge Module | ||
| 86 | // because Merge Modules don't have separate disks. | ||
| 87 | var compare = this.OptimizingMergeModule ? 0 : x.DiskId.CompareTo(y.DiskId); | ||
| 88 | |||
| 89 | if (compare != 0) | ||
| 90 | { | ||
| 91 | return compare; | ||
| 92 | } | ||
| 93 | |||
| 94 | // Next try to group files by target install directory. | ||
| 95 | if (this.ComponentTargetPaths.TryGetValue(x.ComponentRef, out var canonicalX) && | ||
| 96 | this.ComponentTargetPaths.TryGetValue(y.ComponentRef, out var canonicalY)) | ||
| 97 | { | ||
| 98 | compare = String.Compare(canonicalX, canonicalY, StringComparison.Ordinal); | ||
| 99 | |||
| 100 | if (compare != 0) | ||
| 101 | { | ||
| 102 | return compare; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | // TODO: Consider sorting these facades even smarter by file size or file extension | ||
| 107 | // or other creative ideas to get optimal install speed out of MSI. | ||
| 108 | compare = String.Compare(x.FileName, y.FileName, StringComparison.Ordinal); | ||
| 109 | |||
| 110 | if (compare != 0) | ||
| 111 | { | ||
| 112 | return compare; | ||
| 113 | } | ||
| 114 | |||
| 115 | return String.Compare(x.Id, y.Id, StringComparison.Ordinal); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs new file mode 100644 index 00000000..4d849753 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using WixToolset.Data.WindowsInstaller; | ||
| 6 | |||
| 7 | internal class PatchTransform | ||
| 8 | { | ||
| 9 | public PatchTransform(string baseline, WindowsInstallerData transform) | ||
| 10 | { | ||
| 11 | this.Baseline = baseline; | ||
| 12 | this.Transform = transform; | ||
| 13 | } | ||
| 14 | |||
| 15 | public string Baseline { get; } | ||
| 16 | |||
| 17 | public WindowsInstallerData Transform { get; } | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs new file mode 100644 index 00000000..1bd2a427 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | internal class ProcessDependencyReferencesCommand | ||
| 13 | { | ||
| 14 | // The root registry key for the dependency extension. We write to Software\Classes explicitly | ||
| 15 | // based on the current security context instead of HKCR. See | ||
| 16 | // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. | ||
| 17 | private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; | ||
| 18 | private const string RegistryDependents = "Dependents"; | ||
| 19 | |||
| 20 | public ProcessDependencyReferencesCommand(IBackendHelper backendHelper, IntermediateSection section, IEnumerable<WixDependencyRefSymbol> dependencyRefSymbols) | ||
| 21 | { | ||
| 22 | this.BackendHelper = backendHelper; | ||
| 23 | this.Section = section; | ||
| 24 | this.DependencyRefSymbols = dependencyRefSymbols; | ||
| 25 | } | ||
| 26 | |||
| 27 | private IBackendHelper BackendHelper { get; } | ||
| 28 | |||
| 29 | private IntermediateSection Section { get; } | ||
| 30 | |||
| 31 | private IEnumerable<WixDependencyRefSymbol> DependencyRefSymbols { get; } | ||
| 32 | |||
| 33 | public void Execute() | ||
| 34 | { | ||
| 35 | var wixDependencyRows = this.Section.Symbols.OfType<WixDependencySymbol>().ToDictionary(d => d.Id.Id); | ||
| 36 | var wixDependencyProviderRows = this.Section.Symbols.OfType<WixDependencyProviderSymbol>().ToDictionary(d => d.Id.Id); | ||
| 37 | |||
| 38 | // For each relationship, get the provides and requires rows to generate registry values. | ||
| 39 | foreach (var wixDependencyRefRow in this.DependencyRefSymbols) | ||
| 40 | { | ||
| 41 | var providesId = wixDependencyRefRow.WixDependencyProviderRef; | ||
| 42 | var requiresId = wixDependencyRefRow.WixDependencyRef; | ||
| 43 | |||
| 44 | // If we do not find both symbols, skip the registry key generation. | ||
| 45 | if (!wixDependencyRows.TryGetValue(requiresId, out var wixDependencyRow)) | ||
| 46 | { | ||
| 47 | continue; | ||
| 48 | } | ||
| 49 | |||
| 50 | if (!wixDependencyProviderRows.TryGetValue(providesId, out var wixDependencyProviderRow)) | ||
| 51 | { | ||
| 52 | continue; | ||
| 53 | } | ||
| 54 | |||
| 55 | // Format the root registry key using the required provider key and the current provider key. | ||
| 56 | var requiresKey = wixDependencyRow.Id.Id; | ||
| 57 | var providesKey = wixDependencyRow.ProviderKey; | ||
| 58 | var keyRequires = String.Format(@"{0}{1}\{2}\{3}", DependencyRegistryRoot, requiresKey, RegistryDependents, providesKey); | ||
| 59 | |||
| 60 | // Get the component ID from the provider. | ||
| 61 | var componentId = wixDependencyProviderRow.ParentRef; | ||
| 62 | |||
| 63 | var id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "(Default)"); | ||
| 64 | this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) | ||
| 65 | { | ||
| 66 | ComponentRef = componentId, | ||
| 67 | Root = RegistryRootType.MachineUser, | ||
| 68 | Key = keyRequires, | ||
| 69 | Name = "*", | ||
| 70 | }); | ||
| 71 | |||
| 72 | if (!String.IsNullOrEmpty(wixDependencyRow.MinVersion)) | ||
| 73 | { | ||
| 74 | id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MinVersion"); | ||
| 75 | this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) | ||
| 76 | { | ||
| 77 | ComponentRef = componentId, | ||
| 78 | Root = RegistryRootType.MachineUser, | ||
| 79 | Key = keyRequires, | ||
| 80 | Name = "MinVersion", | ||
| 81 | Value = wixDependencyRow.MinVersion | ||
| 82 | }); | ||
| 83 | } | ||
| 84 | |||
| 85 | var maxVersion = (string)wixDependencyRow[3]; | ||
| 86 | if (!String.IsNullOrEmpty(wixDependencyRow.MaxVersion)) | ||
| 87 | { | ||
| 88 | id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MaxVersion"); | ||
| 89 | this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) | ||
| 90 | { | ||
| 91 | ComponentRef = componentId, | ||
| 92 | Root = RegistryRootType.MachineUser, | ||
| 93 | Key = keyRequires, | ||
| 94 | Name = "MaxVersion", | ||
| 95 | Value = wixDependencyRow.MaxVersion | ||
| 96 | }); | ||
| 97 | } | ||
| 98 | |||
| 99 | if (wixDependencyRow.Attributes != WixDependencySymbolAttributes.None) | ||
| 100 | { | ||
| 101 | id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "Attributes"); | ||
| 102 | this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) | ||
| 103 | { | ||
| 104 | ComponentRef = componentId, | ||
| 105 | Root = RegistryRootType.MachineUser, | ||
| 106 | Key = keyRequires, | ||
| 107 | Name = "Attributes", | ||
| 108 | Value = String.Concat("#", (int)wixDependencyRow.Attributes) | ||
| 109 | }); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs new file mode 100644 index 00000000..9a068603 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Xml; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | |||
| 13 | internal class ProcessPackageSoftwareTagsCommand | ||
| 14 | { | ||
| 15 | public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IEnumerable<WixProductTagSymbol> softwareTags, string intermediateFolder) | ||
| 16 | { | ||
| 17 | this.Section = section; | ||
| 18 | this.SoftwareTags = softwareTags; | ||
| 19 | this.IntermediateFolder = intermediateFolder; | ||
| 20 | } | ||
| 21 | |||
| 22 | private string IntermediateFolder { get; } | ||
| 23 | |||
| 24 | private IntermediateSection Section { get; } | ||
| 25 | |||
| 26 | private IEnumerable<WixProductTagSymbol> SoftwareTags { get; } | ||
| 27 | |||
| 28 | public void Execute() | ||
| 29 | { | ||
| 30 | string productName = null; | ||
| 31 | string productVersion = null; | ||
| 32 | string manufacturer = null; | ||
| 33 | string upgradeCode = null; | ||
| 34 | |||
| 35 | var summaryInfo = this.Section.Symbols.OfType<SummaryInformationSymbol>().FirstOrDefault(s => s.PropertyId == SummaryInformationType.PackageCode); | ||
| 36 | var packageCode = NormalizeGuid(summaryInfo?.Value); | ||
| 37 | |||
| 38 | foreach (var property in this.Section.Symbols.OfType<PropertySymbol>()) | ||
| 39 | { | ||
| 40 | switch (property.Id.Id) | ||
| 41 | { | ||
| 42 | case "ProductName": | ||
| 43 | productName = property.Value; | ||
| 44 | break; | ||
| 45 | case "ProductVersion": | ||
| 46 | productVersion = property.Value; | ||
| 47 | break; | ||
| 48 | case "Manufacturer": | ||
| 49 | manufacturer = property.Value; | ||
| 50 | break; | ||
| 51 | case "UpgradeCode": | ||
| 52 | upgradeCode = NormalizeGuid(property.Value); | ||
| 53 | break; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | var fileSymbolsById = this.Section.Symbols.OfType<FileSymbol>().Where(f => f.Id != null).ToDictionary(f => f.Id.Id); | ||
| 58 | |||
| 59 | var workingFolder = Path.Combine(this.IntermediateFolder, "_swidtag"); | ||
| 60 | |||
| 61 | Directory.CreateDirectory(workingFolder); | ||
| 62 | |||
| 63 | foreach (var tagRow in this.SoftwareTags) | ||
| 64 | { | ||
| 65 | if (fileSymbolsById.TryGetValue(tagRow.FileRef, out var fileSymbol)) | ||
| 66 | { | ||
| 67 | var uniqueId = String.Concat("msi:package/", packageCode); | ||
| 68 | var persistentId = String.IsNullOrEmpty(upgradeCode) ? null : String.Concat("msi:upgrade/", upgradeCode); | ||
| 69 | |||
| 70 | // Write the tag file. | ||
| 71 | fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(workingFolder, fileSymbol.Name) }; | ||
| 72 | |||
| 73 | using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create)) | ||
| 74 | { | ||
| 75 | CreateTagFile(fs, uniqueId, productName, productVersion, tagRow.Regid, manufacturer, persistentId); | ||
| 76 | } | ||
| 77 | |||
| 78 | // Ensure the matching "SoftwareIdentificationTag" row exists and | ||
| 79 | // is populated correctly. | ||
| 80 | this.Section.AddSymbol(new SoftwareIdentificationTagSymbol(tagRow.SourceLineNumbers, tagRow.Id) | ||
| 81 | { | ||
| 82 | FileRef = fileSymbol.Id.Id, | ||
| 83 | Regid = tagRow.Regid, | ||
| 84 | TagId = uniqueId, | ||
| 85 | PersistentId = persistentId | ||
| 86 | }); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | private static string NormalizeGuid(string guidString) | ||
| 92 | { | ||
| 93 | if (Guid.TryParse(guidString, out var guid)) | ||
| 94 | { | ||
| 95 | return guid.ToString("D").ToUpperInvariant(); | ||
| 96 | } | ||
| 97 | |||
| 98 | return guidString; | ||
| 99 | } | ||
| 100 | |||
| 101 | private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId) | ||
| 102 | { | ||
| 103 | var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; | ||
| 104 | |||
| 105 | using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) | ||
| 106 | { | ||
| 107 | writer.WriteStartDocument(); | ||
| 108 | writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); | ||
| 109 | writer.WriteAttributeString("tagId", uniqueId); | ||
| 110 | writer.WriteAttributeString("name", name); | ||
| 111 | writer.WriteAttributeString("version", version); | ||
| 112 | writer.WriteAttributeString("versionScheme", versionScheme); | ||
| 113 | |||
| 114 | writer.WriteStartElement("Entity"); | ||
| 115 | writer.WriteAttributeString("name", manufacturer); | ||
| 116 | writer.WriteAttributeString("regid", regid); | ||
| 117 | writer.WriteAttributeString("role", "softwareCreator tagCreator"); | ||
| 118 | writer.WriteEndElement(); // </Entity> | ||
| 119 | |||
| 120 | if (!String.IsNullOrEmpty(persistendId)) | ||
| 121 | { | ||
| 122 | writer.WriteStartElement("Meta"); | ||
| 123 | writer.WriteAttributeString("persistentId", persistendId); | ||
| 124 | writer.WriteEndElement(); // </Meta> | ||
| 125 | } | ||
| 126 | |||
| 127 | writer.WriteEndElement(); // </SoftwareIdentity> | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs new file mode 100644 index 00000000..217609be --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs | |||
| @@ -0,0 +1,101 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | internal class ProcessPropertiesCommand | ||
| 13 | { | ||
| 14 | public ProcessPropertiesCommand(IntermediateSection section, WixPackageSymbol packageSymbol, int fallbackLcid, bool populateDelayedVariables, IBackendHelper backendHelper) | ||
| 15 | { | ||
| 16 | this.Section = section; | ||
| 17 | this.PackageSymbol = packageSymbol; | ||
| 18 | this.FallbackLcid = fallbackLcid; | ||
| 19 | this.PopulateDelayedVariables = populateDelayedVariables; | ||
| 20 | this.BackendHelper = backendHelper; | ||
| 21 | } | ||
| 22 | |||
| 23 | private IntermediateSection Section { get; } | ||
| 24 | |||
| 25 | private WixPackageSymbol PackageSymbol { get; } | ||
| 26 | |||
| 27 | private int FallbackLcid { get; } | ||
| 28 | |||
| 29 | private bool PopulateDelayedVariables { get; } | ||
| 30 | |||
| 31 | private IBackendHelper BackendHelper { get; } | ||
| 32 | |||
| 33 | public Dictionary<string, string> DelayedVariablesCache { get; private set; } | ||
| 34 | |||
| 35 | public string ProductLanguage { get; private set; } | ||
| 36 | |||
| 37 | public void Execute() | ||
| 38 | { | ||
| 39 | PropertySymbol languageSymbol = null; | ||
| 40 | var variableCache = this.PopulateDelayedVariables ? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) : null; | ||
| 41 | |||
| 42 | if (SectionType.Product == this.Section.Type || variableCache != null) | ||
| 43 | { | ||
| 44 | foreach (var propertySymbol in this.Section.Symbols.OfType<PropertySymbol>()) | ||
| 45 | { | ||
| 46 | // Set the ProductCode if it is to be generated. | ||
| 47 | if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal)) | ||
| 48 | { | ||
| 49 | propertySymbol.Value = this.BackendHelper.CreateGuid(); | ||
| 50 | |||
| 51 | #if TODO_PATCHING // Is this still necessary? | ||
| 52 | // Update the target ProductCode in any instance transforms. | ||
| 53 | foreach (SubStorage subStorage in this.Output.SubStorages) | ||
| 54 | { | ||
| 55 | Output subStorageOutput = subStorage.Data; | ||
| 56 | if (OutputType.Transform != subStorageOutput.Type) | ||
| 57 | { | ||
| 58 | continue; | ||
| 59 | } | ||
| 60 | |||
| 61 | Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; | ||
| 62 | foreach (Row row in instanceSummaryInformationTable.Rows) | ||
| 63 | { | ||
| 64 | if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) | ||
| 65 | { | ||
| 66 | row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); | ||
| 67 | break; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | #endif | ||
| 72 | } | ||
| 73 | else if ("ProductLanguage" == propertySymbol.Id.Id) | ||
| 74 | { | ||
| 75 | languageSymbol = propertySymbol; | ||
| 76 | } | ||
| 77 | |||
| 78 | // Add the property name and value to the variableCache. | ||
| 79 | if (variableCache != null) | ||
| 80 | { | ||
| 81 | variableCache[$"property.{propertySymbol.Id.Id}"] = propertySymbol.Value; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | if (this.Section.Type == SectionType.Product && String.IsNullOrEmpty(languageSymbol?.Value)) | ||
| 86 | { | ||
| 87 | if (languageSymbol == null) | ||
| 88 | { | ||
| 89 | languageSymbol = this.Section.AddSymbol(new PropertySymbol(this.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, "ProductLanguage"))); | ||
| 90 | } | ||
| 91 | |||
| 92 | this.PackageSymbol.Language = this.FallbackLcid.ToString(); | ||
| 93 | languageSymbol.Value = this.FallbackLcid.ToString(); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | this.DelayedVariablesCache = variableCache; | ||
| 98 | this.ProductLanguage = languageSymbol?.Value; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs new file mode 100644 index 00000000..039ba495 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Core.Native.Msi; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | using WixToolset.Extensibility.Data; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Defines the file transfers necessary to layout the uncompressed files. | ||
| 17 | /// </summary> | ||
| 18 | internal class ProcessUncompressedFilesCommand | ||
| 19 | { | ||
| 20 | public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver) | ||
| 21 | { | ||
| 22 | this.Section = section; | ||
| 23 | this.BackendHelper = backendHelper; | ||
| 24 | this.PathResolver = pathResolver; | ||
| 25 | } | ||
| 26 | |||
| 27 | private IntermediateSection Section { get; } | ||
| 28 | |||
| 29 | public IBackendHelper BackendHelper { get; } | ||
| 30 | |||
| 31 | public IPathResolver PathResolver { get; } | ||
| 32 | |||
| 33 | public string DatabasePath { private get; set; } | ||
| 34 | |||
| 35 | public IEnumerable<IFileFacade> FileFacades { private get; set; } | ||
| 36 | |||
| 37 | public string LayoutDirectory { private get; set; } | ||
| 38 | |||
| 39 | public bool Compressed { private get; set; } | ||
| 40 | |||
| 41 | public bool LongNamesInImage { private get; set; } | ||
| 42 | |||
| 43 | public Func<MediaSymbol, string, string, string> ResolveMedia { private get; set; } | ||
| 44 | |||
| 45 | public IEnumerable<IFileTransfer> FileTransfers { get; private set; } | ||
| 46 | |||
| 47 | public IEnumerable<ITrackedFile> TrackedFiles { get; private set; } | ||
| 48 | |||
| 49 | public void Execute() | ||
| 50 | { | ||
| 51 | var fileTransfers = new List<IFileTransfer>(); | ||
| 52 | |||
| 53 | var trackedFiles = new List<ITrackedFile>(); | ||
| 54 | |||
| 55 | var directories = new Dictionary<string, IResolvedDirectory>(); | ||
| 56 | |||
| 57 | var mediaRows = this.Section.Symbols.OfType<MediaSymbol>().ToDictionary(t => t.DiskId); | ||
| 58 | |||
| 59 | using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) | ||
| 60 | { | ||
| 61 | using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) | ||
| 62 | { | ||
| 63 | foreach (var directoryRecord in directoryView.Records) | ||
| 64 | { | ||
| 65 | var sourceName = this.BackendHelper.GetMsiFileName(directoryRecord.GetString(3), true, this.LongNamesInImage); | ||
| 66 | |||
| 67 | var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName); | ||
| 68 | |||
| 69 | directories.Add(directoryRecord.GetString(1), resolvedDirectory); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | using (var fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) | ||
| 74 | { | ||
| 75 | using (var fileQueryRecord = new Record(1)) | ||
| 76 | { | ||
| 77 | // for each file in the array of uncompressed files | ||
| 78 | foreach (var facade in this.FileFacades) | ||
| 79 | { | ||
| 80 | var mediaSymbol = mediaRows[facade.DiskId]; | ||
| 81 | string relativeFileLayoutPath = null; | ||
| 82 | var mediaLayoutFolder = mediaSymbol.Layout; | ||
| 83 | |||
| 84 | var mediaLayoutDirectory = this.ResolveMedia(mediaSymbol, mediaLayoutFolder, this.LayoutDirectory); | ||
| 85 | |||
| 86 | // setup up the query record and find the appropriate file in the | ||
| 87 | // previously executed file view | ||
| 88 | fileQueryRecord[1] = facade.Id; | ||
| 89 | fileView.Execute(fileQueryRecord); | ||
| 90 | |||
| 91 | using (var fileRecord = fileView.Fetch()) | ||
| 92 | { | ||
| 93 | if (null == fileRecord) | ||
| 94 | { | ||
| 95 | throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.SourceLineNumber, facade.Id)); | ||
| 96 | } | ||
| 97 | |||
| 98 | relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); | ||
| 99 | } | ||
| 100 | |||
| 101 | // finally put together the base media layout path and the relative file layout path | ||
| 102 | var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); | ||
| 103 | |||
| 104 | var transfer = this.BackendHelper.CreateFileTransfer(facade.SourcePath, fileLayoutPath, false, facade.SourceLineNumber); | ||
| 105 | fileTransfers.Add(transfer); | ||
| 106 | |||
| 107 | // Track the location where the cabinet will be placed. If the transfer is | ||
| 108 | // redundant then then the file should not be cleaned. This is important | ||
| 109 | // because if the source and destination of the transfer is the same, we | ||
| 110 | // don't want to clean the file because we'd be deleting the original | ||
| 111 | // (and that would be bad). | ||
| 112 | var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.SourceLineNumber); | ||
| 113 | tracked.Clean = !transfer.Redundant; | ||
| 114 | |||
| 115 | trackedFiles.Add(tracked); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | this.FileTransfers = fileTransfers; | ||
| 122 | this.TrackedFiles = trackedFiles; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs new file mode 100644 index 00000000..94fa0a6a --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs | |||
| @@ -0,0 +1,714 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Data.WindowsInstaller; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Set sequence numbers for all the actions and create symbols in the output object. | ||
| 16 | /// </summary> | ||
| 17 | internal class SequenceActionsCommand | ||
| 18 | { | ||
| 19 | public SequenceActionsCommand(IMessaging messaging, IntermediateSection section) | ||
| 20 | { | ||
| 21 | this.Messaging = messaging; | ||
| 22 | this.Section = section; | ||
| 23 | |||
| 24 | this.RelativeActionsForActions = new Dictionary<string, RelativeActions>(); | ||
| 25 | } | ||
| 26 | |||
| 27 | private IMessaging Messaging { get; } | ||
| 28 | |||
| 29 | private IntermediateSection Section { get; } | ||
| 30 | |||
| 31 | private Dictionary<string, RelativeActions> RelativeActionsForActions { get; } | ||
| 32 | |||
| 33 | public void Execute() | ||
| 34 | { | ||
| 35 | var requiredActionSymbols = new Dictionary<string, WixActionSymbol>(); | ||
| 36 | |||
| 37 | // Index all the action symbols and look for collisions. | ||
| 38 | foreach (var actionSymbol in this.Section.Symbols.OfType<WixActionSymbol>()) | ||
| 39 | { | ||
| 40 | if (actionSymbol.Overridable) // overridable action | ||
| 41 | { | ||
| 42 | if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol)) | ||
| 43 | { | ||
| 44 | if (collidingActionSymbol.Overridable) | ||
| 45 | { | ||
| 46 | this.Messaging.Write(ErrorMessages.OverridableActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); | ||
| 47 | if (null != collidingActionSymbol.SourceLineNumbers) | ||
| 48 | { | ||
| 49 | this.Messaging.Write(ErrorMessages.OverridableActionCollision2(collidingActionSymbol.SourceLineNumbers)); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | else | ||
| 54 | { | ||
| 55 | requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | else // unsequenced or sequenced action. | ||
| 59 | { | ||
| 60 | // Unsequenced action (allowed for certain standard actions). | ||
| 61 | if (null == actionSymbol.Before && null == actionSymbol.After && !actionSymbol.Sequence.HasValue) | ||
| 62 | { | ||
| 63 | if (WindowsInstallerStandard.TryGetStandardAction(actionSymbol.Id.Id, out var standardAction)) | ||
| 64 | { | ||
| 65 | // Populate the sequence from the standard action | ||
| 66 | actionSymbol.Sequence = standardAction.Sequence; | ||
| 67 | } | ||
| 68 | else // not a supported unscheduled action. | ||
| 69 | { | ||
| 70 | throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol) && !collidingActionSymbol.Overridable) | ||
| 75 | { | ||
| 76 | this.Messaging.Write(ErrorMessages.ActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); | ||
| 77 | if (null != collidingActionSymbol.SourceLineNumbers) | ||
| 78 | { | ||
| 79 | this.Messaging.Write(ErrorMessages.ActionCollision2(collidingActionSymbol.SourceLineNumbers)); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | else | ||
| 83 | { | ||
| 84 | requiredActionSymbols[actionSymbol.Id.Id] = actionSymbol; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | // Get the standard actions required based on symbols in the section. | ||
| 90 | var requiredStandardActions = this.GetRequiredStandardActions(); | ||
| 91 | |||
| 92 | // Add the overridable action symbols that are not overridden to the required action symbols. | ||
| 93 | foreach (var actionSymbol in requiredStandardActions.Values) | ||
| 94 | { | ||
| 95 | if (!requiredActionSymbols.ContainsKey(actionSymbol.Id.Id)) | ||
| 96 | { | ||
| 97 | requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | // Suppress the required actions that are overridable. | ||
| 102 | foreach (var suppressActionSymbol in this.Section.Symbols.OfType<WixSuppressActionSymbol>()) | ||
| 103 | { | ||
| 104 | var key = suppressActionSymbol.Id.Id; | ||
| 105 | |||
| 106 | // If there is an overridable symbol to suppress; suppress it. There is no warning if there | ||
| 107 | // is no action to suppress because the action may be suppressed from a merge module in | ||
| 108 | // the binder. | ||
| 109 | if (requiredActionSymbols.TryGetValue(key, out var requiredActionSymbol)) | ||
| 110 | { | ||
| 111 | if (requiredActionSymbol.Overridable) | ||
| 112 | { | ||
| 113 | this.Messaging.Write(WarningMessages.SuppressAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.Action, suppressActionSymbol.SequenceTable.ToString())); | ||
| 114 | if (null != requiredActionSymbol.SourceLineNumbers) | ||
| 115 | { | ||
| 116 | this.Messaging.Write(WarningMessages.SuppressAction2(requiredActionSymbol.SourceLineNumbers)); | ||
| 117 | } | ||
| 118 | |||
| 119 | requiredActionSymbols.Remove(key); | ||
| 120 | } | ||
| 121 | else // suppressing a non-overridable action symbol | ||
| 122 | { | ||
| 123 | this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.SequenceTable.ToString(), suppressActionSymbol.Action)); | ||
| 124 | if (null != requiredActionSymbol.SourceLineNumbers) | ||
| 125 | { | ||
| 126 | this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction2(requiredActionSymbol.SourceLineNumbers)); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | // A dictionary used for detecting cyclic references among action symbols. | ||
| 133 | var firstReference = new Dictionary<WixActionSymbol, WixActionSymbol>(); | ||
| 134 | |||
| 135 | // Build up dependency trees of the relatively scheduled actions. | ||
| 136 | // Use ToList() to create a copy of the required action symbols so that new symbols can | ||
| 137 | // be added while enumerating. | ||
| 138 | foreach (var actionSymbol in requiredActionSymbols.Values.ToList()) | ||
| 139 | { | ||
| 140 | if (!actionSymbol.Sequence.HasValue) | ||
| 141 | { | ||
| 142 | // check for standard actions that don't have a sequence number in a merge module | ||
| 143 | if (SectionType.Module == this.Section.Type && WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) | ||
| 144 | { | ||
| 145 | this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); | ||
| 146 | } | ||
| 147 | |||
| 148 | this.SequenceActionSymbol(actionSymbol, requiredActionSymbols, firstReference); | ||
| 149 | } | ||
| 150 | else if (SectionType.Module == this.Section.Type && 0 < actionSymbol.Sequence && !WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) // check for custom actions and dialogs that have a sequence number | ||
| 151 | { | ||
| 152 | this.Messaging.Write(ErrorMessages.CustomActionSequencedInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | // Look for standard actions with sequence restrictions that aren't necessarily scheduled based | ||
| 157 | // on the presence of a particular table. | ||
| 158 | if (requiredActionSymbols.ContainsKey("InstallExecuteSequence/DuplicateFiles") && !requiredActionSymbols.ContainsKey("InstallExecuteSequence/InstallFiles")) | ||
| 159 | { | ||
| 160 | WindowsInstallerStandard.TryGetStandardAction("InstallExecuteSequence/InstallFiles", out var standardAction); | ||
| 161 | requiredActionSymbols.Add(standardAction.Id.Id, standardAction); | ||
| 162 | } | ||
| 163 | |||
| 164 | // Schedule actions. | ||
| 165 | List<WixActionSymbol> scheduledActionSymbols; | ||
| 166 | if (SectionType.Module == this.Section.Type) | ||
| 167 | { | ||
| 168 | scheduledActionSymbols = requiredActionSymbols.Values.ToList(); | ||
| 169 | } | ||
| 170 | else | ||
| 171 | { | ||
| 172 | scheduledActionSymbols = this.ScheduleActions(requiredActionSymbols); | ||
| 173 | } | ||
| 174 | |||
| 175 | // Remove all existing WixActionSymbols from the section then add the | ||
| 176 | // scheduled actions back to the section. | ||
| 177 | var removeActionSymbols = this.Section.Symbols.Where(s => s.Definition.Type == SymbolDefinitionType.WixAction).ToList(); | ||
| 178 | |||
| 179 | foreach (var removeSymbol in removeActionSymbols) | ||
| 180 | { | ||
| 181 | this.Section.RemoveSymbol(removeSymbol); | ||
| 182 | } | ||
| 183 | |||
| 184 | foreach (var action in scheduledActionSymbols) | ||
| 185 | { | ||
| 186 | this.Section.AddSymbol(action); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | private Dictionary<string, WixActionSymbol> GetRequiredStandardActions() | ||
| 191 | { | ||
| 192 | var overridableActionSymbols = new Dictionary<string, WixActionSymbol>(); | ||
| 193 | |||
| 194 | var requiredActionIds = this.GetRequiredActionIds(); | ||
| 195 | |||
| 196 | foreach (var actionId in requiredActionIds) | ||
| 197 | { | ||
| 198 | WindowsInstallerStandard.TryGetStandardAction(actionId, out var standardAction); | ||
| 199 | overridableActionSymbols.Add(standardAction.Id.Id, standardAction); | ||
| 200 | } | ||
| 201 | |||
| 202 | return overridableActionSymbols; | ||
| 203 | } | ||
| 204 | |||
| 205 | private List<WixActionSymbol> ScheduleActions(Dictionary<string, WixActionSymbol> requiredActionSymbols) | ||
| 206 | { | ||
| 207 | var scheduledActionSymbols = new List<WixActionSymbol>(); | ||
| 208 | |||
| 209 | // Process each sequence table individually. | ||
| 210 | foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) | ||
| 211 | { | ||
| 212 | // Create a collection of just the action symbols in this sequence | ||
| 213 | var sequenceActionSymbols = requiredActionSymbols.Values.Where(a => a.SequenceTable == sequenceTable).ToList(); | ||
| 214 | |||
| 215 | // Schedule the absolutely scheduled actions (by sorting them by their sequence numbers). | ||
| 216 | var absoluteActionSymbols = new List<WixActionSymbol>(); | ||
| 217 | foreach (var actionSymbol in sequenceActionSymbols) | ||
| 218 | { | ||
| 219 | if (actionSymbol.Sequence.HasValue) | ||
| 220 | { | ||
| 221 | // Look for sequence number collisions | ||
| 222 | foreach (var sequenceScheduledActionSymbol in absoluteActionSymbols) | ||
| 223 | { | ||
| 224 | if (sequenceScheduledActionSymbol.Sequence == actionSymbol.Sequence) | ||
| 225 | { | ||
| 226 | this.Messaging.Write(WarningMessages.ActionSequenceCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, sequenceScheduledActionSymbol.Action, actionSymbol.Sequence ?? 0)); | ||
| 227 | if (null != sequenceScheduledActionSymbol.SourceLineNumbers) | ||
| 228 | { | ||
| 229 | this.Messaging.Write(WarningMessages.ActionSequenceCollision2(sequenceScheduledActionSymbol.SourceLineNumbers)); | ||
| 230 | } | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | absoluteActionSymbols.Add(actionSymbol); | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | absoluteActionSymbols.Sort((x, y) => (x.Sequence ?? 0).CompareTo(y.Sequence ?? 0)); | ||
| 239 | |||
| 240 | // Schedule the relatively scheduled actions (by resolving the dependency trees). | ||
| 241 | var previousUsedSequence = 0; | ||
| 242 | var relativeActionSymbols = new List<WixActionSymbol>(); | ||
| 243 | for (int j = 0; j < absoluteActionSymbols.Count; j++) | ||
| 244 | { | ||
| 245 | var absoluteActionSymbol = absoluteActionSymbols[j]; | ||
| 246 | |||
| 247 | // Get all the relatively scheduled action symbols occuring before and after this absolutely scheduled action symbol. | ||
| 248 | var relativeActions = this.GetAllRelativeActionsForSequenceType(sequenceTable, absoluteActionSymbol); | ||
| 249 | |||
| 250 | // Check for relatively scheduled actions occuring before/after a special action | ||
| 251 | // (those actions with a negative sequence number). | ||
| 252 | if (absoluteActionSymbol.Sequence < 0 && (relativeActions.PreviousActions.Any() || relativeActions.NextActions.Any())) | ||
| 253 | { | ||
| 254 | // Create errors for all the before actions. | ||
| 255 | foreach (var actionSymbol in relativeActions.PreviousActions) | ||
| 256 | { | ||
| 257 | this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); | ||
| 258 | } | ||
| 259 | |||
| 260 | // Create errors for all the after actions. | ||
| 261 | foreach (var actionSymbol in relativeActions.NextActions) | ||
| 262 | { | ||
| 263 | this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); | ||
| 264 | } | ||
| 265 | |||
| 266 | // If there is source line information for the absolutely scheduled action display it | ||
| 267 | if (absoluteActionSymbol.SourceLineNumbers != null) | ||
| 268 | { | ||
| 269 | this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction2(absoluteActionSymbol.SourceLineNumbers)); | ||
| 270 | } | ||
| 271 | |||
| 272 | continue; | ||
| 273 | } | ||
| 274 | |||
| 275 | // Schedule the action symbols before this one. | ||
| 276 | var unusedSequence = absoluteActionSymbol.Sequence - 1; | ||
| 277 | for (var i = relativeActions.PreviousActions.Count - 1; i >= 0; i--) | ||
| 278 | { | ||
| 279 | var relativeActionSymbol = relativeActions.PreviousActions[i]; | ||
| 280 | |||
| 281 | // look for collisions | ||
| 282 | if (unusedSequence == previousUsedSequence) | ||
| 283 | { | ||
| 284 | this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); | ||
| 285 | if (absoluteActionSymbol.SourceLineNumbers != null) | ||
| 286 | { | ||
| 287 | this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); | ||
| 288 | } | ||
| 289 | |||
| 290 | unusedSequence++; | ||
| 291 | } | ||
| 292 | |||
| 293 | relativeActionSymbol.Sequence = unusedSequence; | ||
| 294 | relativeActionSymbols.Add(relativeActionSymbol); | ||
| 295 | |||
| 296 | unusedSequence--; | ||
| 297 | } | ||
| 298 | |||
| 299 | // Determine the next used action sequence number. | ||
| 300 | var nextUsedSequence = Int16.MaxValue + 1; | ||
| 301 | if (absoluteActionSymbols.Count > j + 1) | ||
| 302 | { | ||
| 303 | nextUsedSequence = absoluteActionSymbols[j + 1].Sequence ?? 0; | ||
| 304 | } | ||
| 305 | |||
| 306 | // Schedule the action symbols after this one. | ||
| 307 | unusedSequence = absoluteActionSymbol.Sequence + 1; | ||
| 308 | for (var i = 0; i < relativeActions.NextActions.Count; i++) | ||
| 309 | { | ||
| 310 | var relativeActionSymbol = relativeActions.NextActions[i]; | ||
| 311 | |||
| 312 | if (unusedSequence == nextUsedSequence) | ||
| 313 | { | ||
| 314 | this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); | ||
| 315 | if (absoluteActionSymbol.SourceLineNumbers != null) | ||
| 316 | { | ||
| 317 | this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); | ||
| 318 | } | ||
| 319 | |||
| 320 | unusedSequence--; | ||
| 321 | } | ||
| 322 | |||
| 323 | relativeActionSymbol.Sequence = unusedSequence; | ||
| 324 | relativeActionSymbols.Add(relativeActionSymbol); | ||
| 325 | |||
| 326 | unusedSequence++; | ||
| 327 | } | ||
| 328 | |||
| 329 | // keep track of this sequence number as the previous used sequence number for the next iteration | ||
| 330 | previousUsedSequence = absoluteActionSymbol.Sequence ?? 0; | ||
| 331 | } | ||
| 332 | |||
| 333 | // add the absolutely and relatively scheduled actions to the list of scheduled actions | ||
| 334 | scheduledActionSymbols.AddRange(absoluteActionSymbols); | ||
| 335 | scheduledActionSymbols.AddRange(relativeActionSymbols); | ||
| 336 | } | ||
| 337 | |||
| 338 | return scheduledActionSymbols; | ||
| 339 | } | ||
| 340 | |||
| 341 | private IEnumerable<string> GetRequiredActionIds() | ||
| 342 | { | ||
| 343 | var set = new HashSet<string>(); | ||
| 344 | |||
| 345 | // gather the required actions for the output type | ||
| 346 | if (SectionType.Product == this.Section.Type) | ||
| 347 | { | ||
| 348 | // AdminExecuteSequence table | ||
| 349 | set.Add("AdminExecuteSequence/CostFinalize"); | ||
| 350 | set.Add("AdminExecuteSequence/CostInitialize"); | ||
| 351 | set.Add("AdminExecuteSequence/FileCost"); | ||
| 352 | set.Add("AdminExecuteSequence/InstallAdminPackage"); | ||
| 353 | set.Add("AdminExecuteSequence/InstallFiles"); | ||
| 354 | set.Add("AdminExecuteSequence/InstallFinalize"); | ||
| 355 | set.Add("AdminExecuteSequence/InstallInitialize"); | ||
| 356 | set.Add("AdminExecuteSequence/InstallValidate"); | ||
| 357 | |||
| 358 | // AdminUISequence table | ||
| 359 | set.Add("AdminUISequence/CostFinalize"); | ||
| 360 | set.Add("AdminUISequence/CostInitialize"); | ||
| 361 | set.Add("AdminUISequence/ExecuteAction"); | ||
| 362 | set.Add("AdminUISequence/FileCost"); | ||
| 363 | |||
| 364 | // AdvtExecuteSequence table | ||
| 365 | set.Add("AdvertiseExecuteSequence/CostFinalize"); | ||
| 366 | set.Add("AdvertiseExecuteSequence/CostInitialize"); | ||
| 367 | set.Add("AdvertiseExecuteSequence/InstallInitialize"); | ||
| 368 | set.Add("AdvertiseExecuteSequence/InstallFinalize"); | ||
| 369 | set.Add("AdvertiseExecuteSequence/InstallValidate"); | ||
| 370 | set.Add("AdvertiseExecuteSequence/PublishFeatures"); | ||
| 371 | set.Add("AdvertiseExecuteSequence/PublishProduct"); | ||
| 372 | |||
| 373 | // InstallExecuteSequence table | ||
| 374 | set.Add("InstallExecuteSequence/CostFinalize"); | ||
| 375 | set.Add("InstallExecuteSequence/CostInitialize"); | ||
| 376 | set.Add("InstallExecuteSequence/FileCost"); | ||
| 377 | set.Add("InstallExecuteSequence/InstallFinalize"); | ||
| 378 | set.Add("InstallExecuteSequence/InstallInitialize"); | ||
| 379 | set.Add("InstallExecuteSequence/InstallValidate"); | ||
| 380 | set.Add("InstallExecuteSequence/ProcessComponents"); | ||
| 381 | set.Add("InstallExecuteSequence/PublishFeatures"); | ||
| 382 | set.Add("InstallExecuteSequence/PublishProduct"); | ||
| 383 | set.Add("InstallExecuteSequence/RegisterProduct"); | ||
| 384 | set.Add("InstallExecuteSequence/RegisterUser"); | ||
| 385 | set.Add("InstallExecuteSequence/UnpublishFeatures"); | ||
| 386 | set.Add("InstallExecuteSequence/ValidateProductID"); | ||
| 387 | |||
| 388 | // InstallUISequence table | ||
| 389 | set.Add("InstallUISequence/CostFinalize"); | ||
| 390 | set.Add("InstallUISequence/CostInitialize"); | ||
| 391 | set.Add("InstallUISequence/ExecuteAction"); | ||
| 392 | set.Add("InstallUISequence/FileCost"); | ||
| 393 | set.Add("InstallUISequence/ValidateProductID"); | ||
| 394 | } | ||
| 395 | |||
| 396 | // Gather the required actions for each symbol type. | ||
| 397 | foreach (var symbolType in this.Section.Symbols.Select(t => t.Definition.Type).Distinct()) | ||
| 398 | { | ||
| 399 | switch (symbolType) | ||
| 400 | { | ||
| 401 | case SymbolDefinitionType.AppSearch: | ||
| 402 | set.Add("InstallExecuteSequence/AppSearch"); | ||
| 403 | set.Add("InstallUISequence/AppSearch"); | ||
| 404 | break; | ||
| 405 | case SymbolDefinitionType.CCPSearch: | ||
| 406 | set.Add("InstallExecuteSequence/AppSearch"); | ||
| 407 | set.Add("InstallExecuteSequence/CCPSearch"); | ||
| 408 | set.Add("InstallExecuteSequence/RMCCPSearch"); | ||
| 409 | set.Add("InstallUISequence/AppSearch"); | ||
| 410 | set.Add("InstallUISequence/CCPSearch"); | ||
| 411 | set.Add("InstallUISequence/RMCCPSearch"); | ||
| 412 | break; | ||
| 413 | case SymbolDefinitionType.Class: | ||
| 414 | set.Add("AdvertiseExecuteSequence/RegisterClassInfo"); | ||
| 415 | set.Add("InstallExecuteSequence/RegisterClassInfo"); | ||
| 416 | set.Add("InstallExecuteSequence/UnregisterClassInfo"); | ||
| 417 | break; | ||
| 418 | case SymbolDefinitionType.Complus: | ||
| 419 | set.Add("InstallExecuteSequence/RegisterComPlus"); | ||
| 420 | set.Add("InstallExecuteSequence/UnregisterComPlus"); | ||
| 421 | break; | ||
| 422 | case SymbolDefinitionType.Component: | ||
| 423 | case SymbolDefinitionType.CreateFolder: | ||
| 424 | set.Add("InstallExecuteSequence/CreateFolders"); | ||
| 425 | set.Add("InstallExecuteSequence/RemoveFolders"); | ||
| 426 | break; | ||
| 427 | case SymbolDefinitionType.DuplicateFile: | ||
| 428 | set.Add("InstallExecuteSequence/DuplicateFiles"); | ||
| 429 | set.Add("InstallExecuteSequence/RemoveDuplicateFiles"); | ||
| 430 | break; | ||
| 431 | case SymbolDefinitionType.Environment: | ||
| 432 | set.Add("InstallExecuteSequence/WriteEnvironmentStrings"); | ||
| 433 | set.Add("InstallExecuteSequence/RemoveEnvironmentStrings"); | ||
| 434 | break; | ||
| 435 | case SymbolDefinitionType.Extension: | ||
| 436 | set.Add("AdvertiseExecuteSequence/RegisterExtensionInfo"); | ||
| 437 | set.Add("InstallExecuteSequence/RegisterExtensionInfo"); | ||
| 438 | set.Add("InstallExecuteSequence/UnregisterExtensionInfo"); | ||
| 439 | break; | ||
| 440 | case SymbolDefinitionType.File: | ||
| 441 | set.Add("InstallExecuteSequence/InstallFiles"); | ||
| 442 | set.Add("InstallExecuteSequence/RemoveFiles"); | ||
| 443 | |||
| 444 | var foundFont = false; | ||
| 445 | var foundSelfReg = false; | ||
| 446 | var foundBindPath = false; | ||
| 447 | foreach (var file in this.Section.Symbols.OfType<FileSymbol>()) | ||
| 448 | { | ||
| 449 | if (!foundFont && !String.IsNullOrEmpty(file.FontTitle)) | ||
| 450 | { | ||
| 451 | set.Add("InstallExecuteSequence/RegisterFonts"); | ||
| 452 | set.Add("InstallExecuteSequence/UnregisterFonts"); | ||
| 453 | foundFont = true; | ||
| 454 | } | ||
| 455 | |||
| 456 | if (!foundSelfReg && file.SelfRegCost.HasValue) | ||
| 457 | { | ||
| 458 | set.Add("InstallExecuteSequence/SelfRegModules"); | ||
| 459 | set.Add("InstallExecuteSequence/SelfUnregModules"); | ||
| 460 | foundSelfReg = true; | ||
| 461 | } | ||
| 462 | |||
| 463 | if (!foundBindPath && !String.IsNullOrEmpty(file.BindPath)) | ||
| 464 | { | ||
| 465 | set.Add("InstallExecuteSequence/BindImage"); | ||
| 466 | foundBindPath = true; | ||
| 467 | } | ||
| 468 | } | ||
| 469 | break; | ||
| 470 | case SymbolDefinitionType.IniFile: | ||
| 471 | set.Add("InstallExecuteSequence/WriteIniValues"); | ||
| 472 | set.Add("InstallExecuteSequence/RemoveIniValues"); | ||
| 473 | break; | ||
| 474 | case SymbolDefinitionType.IsolatedComponent: | ||
| 475 | set.Add("InstallExecuteSequence/IsolateComponents"); | ||
| 476 | break; | ||
| 477 | case SymbolDefinitionType.LaunchCondition: | ||
| 478 | set.Add("InstallExecuteSequence/LaunchConditions"); | ||
| 479 | set.Add("InstallUISequence/LaunchConditions"); | ||
| 480 | break; | ||
| 481 | case SymbolDefinitionType.MIME: | ||
| 482 | set.Add("AdvertiseExecuteSequence/RegisterMIMEInfo"); | ||
| 483 | set.Add("InstallExecuteSequence/RegisterMIMEInfo"); | ||
| 484 | set.Add("InstallExecuteSequence/UnregisterMIMEInfo"); | ||
| 485 | break; | ||
| 486 | case SymbolDefinitionType.MoveFile: | ||
| 487 | set.Add("InstallExecuteSequence/MoveFiles"); | ||
| 488 | break; | ||
| 489 | case SymbolDefinitionType.Assembly: | ||
| 490 | set.Add("AdvertiseExecuteSequence/MsiPublishAssemblies"); | ||
| 491 | set.Add("InstallExecuteSequence/MsiPublishAssemblies"); | ||
| 492 | set.Add("InstallExecuteSequence/MsiUnpublishAssemblies"); | ||
| 493 | break; | ||
| 494 | case SymbolDefinitionType.MsiServiceConfig: | ||
| 495 | case SymbolDefinitionType.MsiServiceConfigFailureActions: | ||
| 496 | set.Add("InstallExecuteSequence/MsiConfigureServices"); | ||
| 497 | break; | ||
| 498 | case SymbolDefinitionType.ODBCDataSource: | ||
| 499 | case SymbolDefinitionType.ODBCTranslator: | ||
| 500 | case SymbolDefinitionType.ODBCDriver: | ||
| 501 | set.Add("InstallExecuteSequence/SetODBCFolders"); | ||
| 502 | set.Add("InstallExecuteSequence/InstallODBC"); | ||
| 503 | set.Add("InstallExecuteSequence/RemoveODBC"); | ||
| 504 | break; | ||
| 505 | case SymbolDefinitionType.ProgId: | ||
| 506 | set.Add("AdvertiseExecuteSequence/RegisterProgIdInfo"); | ||
| 507 | set.Add("InstallExecuteSequence/RegisterProgIdInfo"); | ||
| 508 | set.Add("InstallExecuteSequence/UnregisterProgIdInfo"); | ||
| 509 | break; | ||
| 510 | case SymbolDefinitionType.PublishComponent: | ||
| 511 | set.Add("AdvertiseExecuteSequence/PublishComponents"); | ||
| 512 | set.Add("InstallExecuteSequence/PublishComponents"); | ||
| 513 | set.Add("InstallExecuteSequence/UnpublishComponents"); | ||
| 514 | break; | ||
| 515 | case SymbolDefinitionType.Registry: | ||
| 516 | case SymbolDefinitionType.RemoveRegistry: | ||
| 517 | set.Add("InstallExecuteSequence/WriteRegistryValues"); | ||
| 518 | set.Add("InstallExecuteSequence/RemoveRegistryValues"); | ||
| 519 | break; | ||
| 520 | case SymbolDefinitionType.RemoveFile: | ||
| 521 | set.Add("InstallExecuteSequence/RemoveFiles"); | ||
| 522 | break; | ||
| 523 | case SymbolDefinitionType.ServiceControl: | ||
| 524 | set.Add("InstallExecuteSequence/StartServices"); | ||
| 525 | set.Add("InstallExecuteSequence/StopServices"); | ||
| 526 | set.Add("InstallExecuteSequence/DeleteServices"); | ||
| 527 | break; | ||
| 528 | case SymbolDefinitionType.ServiceInstall: | ||
| 529 | set.Add("InstallExecuteSequence/InstallServices"); | ||
| 530 | break; | ||
| 531 | case SymbolDefinitionType.Shortcut: | ||
| 532 | set.Add("AdvertiseExecuteSequence/CreateShortcuts"); | ||
| 533 | set.Add("InstallExecuteSequence/CreateShortcuts"); | ||
| 534 | set.Add("InstallExecuteSequence/RemoveShortcuts"); | ||
| 535 | break; | ||
| 536 | case SymbolDefinitionType.TypeLib: | ||
| 537 | set.Add("InstallExecuteSequence/RegisterTypeLibraries"); | ||
| 538 | set.Add("InstallExecuteSequence/UnregisterTypeLibraries"); | ||
| 539 | break; | ||
| 540 | case SymbolDefinitionType.Upgrade: | ||
| 541 | set.Add("InstallExecuteSequence/FindRelatedProducts"); | ||
| 542 | set.Add("InstallUISequence/FindRelatedProducts"); | ||
| 543 | |||
| 544 | // Only add the MigrateFeatureStates action if MigrateFeature attribute is set on | ||
| 545 | // at least one UpgradeVersion element. | ||
| 546 | if (this.Section.Symbols.OfType<UpgradeSymbol>().Any(t => t.MigrateFeatures)) | ||
| 547 | { | ||
| 548 | set.Add("InstallExecuteSequence/MigrateFeatureStates"); | ||
| 549 | set.Add("InstallUISequence/MigrateFeatureStates"); | ||
| 550 | } | ||
| 551 | break; | ||
| 552 | } | ||
| 553 | } | ||
| 554 | |||
| 555 | return set; | ||
| 556 | } | ||
| 557 | |||
| 558 | /// <summary> | ||
| 559 | /// Sequence an action before or after a standard action. | ||
| 560 | /// </summary> | ||
| 561 | /// <param name="actionSymbol">The action symbol to be sequenced.</param> | ||
| 562 | /// <param name="requiredActionSymbols">Collection of actions which must be included.</param> | ||
| 563 | /// <param name="firstReference">A dictionary used for detecting cyclic references among action symbols.</param> | ||
| 564 | private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols, Dictionary<WixActionSymbol, WixActionSymbol> firstReference) | ||
| 565 | { | ||
| 566 | var after = false; | ||
| 567 | |||
| 568 | if (actionSymbol.After != null) | ||
| 569 | { | ||
| 570 | after = true; | ||
| 571 | } | ||
| 572 | else if (actionSymbol.Before == null) | ||
| 573 | { | ||
| 574 | throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); | ||
| 575 | } | ||
| 576 | |||
| 577 | var parentActionName = (after ? actionSymbol.After : actionSymbol.Before); | ||
| 578 | var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + parentActionName; | ||
| 579 | |||
| 580 | if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) | ||
| 581 | { | ||
| 582 | // If the missing parent action is a standard action (with a suggested sequence number), add it. | ||
| 583 | if (WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol)) | ||
| 584 | { | ||
| 585 | // Create a clone to avoid modifying the static copy of the object. | ||
| 586 | // TODO: consider this: parentActionSymbol = parentActionSymbol.Clone(); | ||
| 587 | |||
| 588 | requiredActionSymbols.Add(parentActionSymbol.Id.Id, parentActionSymbol); | ||
| 589 | } | ||
| 590 | else | ||
| 591 | { | ||
| 592 | throw new WixException($"Found action {actionSymbol.Id.Id} with a non-existent {(after ? "After" : "Before")} action '{parentActionName}'. The linker should have prevented this."); | ||
| 593 | } | ||
| 594 | } | ||
| 595 | |||
| 596 | this.CheckForCircularActionReference(actionSymbol, requiredActionSymbols, firstReference); | ||
| 597 | |||
| 598 | // Add this action to the appropriate list of dependent action symbols. | ||
| 599 | var relativeActions = this.GetRelativeActions(parentActionSymbol); | ||
| 600 | var relatedSymbols = (after ? relativeActions.NextActions : relativeActions.PreviousActions); | ||
| 601 | relatedSymbols.Add(actionSymbol); | ||
| 602 | } | ||
| 603 | |||
| 604 | /// <summary> | ||
| 605 | /// Check the specified action symbol to see if it leads to a cycle. | ||
| 606 | /// </summary> | ||
| 607 | /// <para> Use the provided dictionary to note the initial action symbol that first led to each action | ||
| 608 | /// symbol. Any action symbol encountered that has already been encountered starting from a different | ||
| 609 | /// initial action symbol inherits the loop characteristics of that initial action symbol, and thus is | ||
| 610 | /// also not part of a cycle. However, any action symbol encountered that has already been encountered | ||
| 611 | /// starting from the same initial action symbol is an indication that the current action symbol is | ||
| 612 | /// part of a cycle. | ||
| 613 | /// </para> | ||
| 614 | /// <param name="actionSymbol">The action symbol to be checked.</param> | ||
| 615 | /// <param name="requiredActionSymbols">Collection of actions which must be included.</param> | ||
| 616 | /// <param name="firstReference">The first encountered action symbol that led to each action symbol.</param> | ||
| 617 | private void CheckForCircularActionReference(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols, Dictionary<WixActionSymbol, WixActionSymbol> firstReference) | ||
| 618 | { | ||
| 619 | WixActionSymbol currentActionSymbol = null; | ||
| 620 | var parentActionSymbol = actionSymbol; | ||
| 621 | |||
| 622 | do | ||
| 623 | { | ||
| 624 | var previousActionSymbol = currentActionSymbol ?? parentActionSymbol; | ||
| 625 | currentActionSymbol = parentActionSymbol; | ||
| 626 | |||
| 627 | if (!firstReference.TryGetValue(currentActionSymbol, out var existingInitialActionSymbol)) | ||
| 628 | { | ||
| 629 | firstReference[currentActionSymbol] = actionSymbol; | ||
| 630 | } | ||
| 631 | else if (existingInitialActionSymbol == actionSymbol) | ||
| 632 | { | ||
| 633 | this.Messaging.Write(ErrorMessages.ActionCircularDependency(currentActionSymbol.SourceLineNumbers, currentActionSymbol.SequenceTable.ToString(), currentActionSymbol.Action, previousActionSymbol.Action)); | ||
| 634 | } | ||
| 635 | |||
| 636 | parentActionSymbol = this.GetParentActionSymbol(currentActionSymbol, requiredActionSymbols); | ||
| 637 | } while (null != parentActionSymbol && !this.Messaging.EncounteredError); | ||
| 638 | } | ||
| 639 | |||
| 640 | /// <summary> | ||
| 641 | /// Get the action symbol that is the parent of the given action symbol. | ||
| 642 | /// </summary> | ||
| 643 | /// <param name="actionSymbol">The given action symbol.</param> | ||
| 644 | /// <param name="requiredActionSymbols">Collection of actions which must be included.</param> | ||
| 645 | /// <returns>Null if there is no parent. Used for loop termination.</returns> | ||
| 646 | private WixActionSymbol GetParentActionSymbol(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols) | ||
| 647 | { | ||
| 648 | if (null == actionSymbol.Before && null == actionSymbol.After) | ||
| 649 | { | ||
| 650 | return null; | ||
| 651 | } | ||
| 652 | |||
| 653 | var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + (actionSymbol.After ?? actionSymbol.Before); | ||
| 654 | |||
| 655 | if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) | ||
| 656 | { | ||
| 657 | WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol); | ||
| 658 | } | ||
| 659 | |||
| 660 | return parentActionSymbol; | ||
| 661 | } | ||
| 662 | |||
| 663 | |||
| 664 | private RelativeActions GetRelativeActions(WixActionSymbol action) | ||
| 665 | { | ||
| 666 | if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions)) | ||
| 667 | { | ||
| 668 | relativeActions = new RelativeActions(); | ||
| 669 | this.RelativeActionsForActions.Add(action.Id.Id, relativeActions); | ||
| 670 | } | ||
| 671 | |||
| 672 | return relativeActions; | ||
| 673 | } | ||
| 674 | |||
| 675 | private RelativeActions GetAllRelativeActionsForSequenceType(SequenceTable sequenceType, WixActionSymbol action) | ||
| 676 | { | ||
| 677 | var relativeActions = new RelativeActions(); | ||
| 678 | |||
| 679 | if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) | ||
| 680 | { | ||
| 681 | this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, relativeActions.PreviousActions); | ||
| 682 | |||
| 683 | this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, relativeActions.NextActions); | ||
| 684 | } | ||
| 685 | |||
| 686 | return relativeActions; | ||
| 687 | } | ||
| 688 | |||
| 689 | private void RecurseRelativeActionsForSequenceType(SequenceTable sequenceType, List<WixActionSymbol> actions, List<WixActionSymbol> visitedActions) | ||
| 690 | { | ||
| 691 | foreach (var action in actions.Where(a => a.SequenceTable == sequenceType)) | ||
| 692 | { | ||
| 693 | if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) | ||
| 694 | { | ||
| 695 | this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, visitedActions); | ||
| 696 | } | ||
| 697 | |||
| 698 | visitedActions.Add(action); | ||
| 699 | |||
| 700 | if (actionRelatives != null) | ||
| 701 | { | ||
| 702 | this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, visitedActions); | ||
| 703 | } | ||
| 704 | } | ||
| 705 | } | ||
| 706 | |||
| 707 | private class RelativeActions | ||
| 708 | { | ||
| 709 | public List<WixActionSymbol> PreviousActions { get; } = new List<WixActionSymbol>(); | ||
| 710 | |||
| 711 | public List<WixActionSymbol> NextActions { get; } = new List<WixActionSymbol>(); | ||
| 712 | } | ||
| 713 | } | ||
| 714 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs new file mode 100644 index 00000000..0f77abfc --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs | |||
| @@ -0,0 +1,365 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.ComponentModel; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using WixToolset.Core.Native.Msi; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Extensibility.Data; | ||
| 15 | using WixToolset.Extensibility.Services; | ||
| 16 | |||
| 17 | /// <summary> | ||
| 18 | /// Update file information. | ||
| 19 | /// </summary> | ||
| 20 | internal class UpdateFileFacadesCommand | ||
| 21 | { | ||
| 22 | public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable<IFileFacade> fileFacades, IEnumerable<IFileFacade> updateFileFacades, IDictionary<string, string> variableCache, bool overwriteHash) | ||
| 23 | { | ||
| 24 | this.Messaging = messaging; | ||
| 25 | this.Section = section; | ||
| 26 | this.FileFacades = fileFacades; | ||
| 27 | this.UpdateFileFacades = updateFileFacades; | ||
| 28 | this.VariableCache = variableCache; | ||
| 29 | this.OverwriteHash = overwriteHash; | ||
| 30 | } | ||
| 31 | |||
| 32 | private IMessaging Messaging { get; } | ||
| 33 | |||
| 34 | private IntermediateSection Section { get; } | ||
| 35 | |||
| 36 | private IEnumerable<IFileFacade> FileFacades { get; } | ||
| 37 | |||
| 38 | private IEnumerable<IFileFacade> UpdateFileFacades { get; } | ||
| 39 | |||
| 40 | private bool OverwriteHash { get; } | ||
| 41 | |||
| 42 | private IDictionary<string, string> VariableCache { get; } | ||
| 43 | |||
| 44 | public void Execute() | ||
| 45 | { | ||
| 46 | var assemblyNameSymbols = this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id); | ||
| 47 | |||
| 48 | foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) | ||
| 49 | { | ||
| 50 | this.UpdateFileFacade(file, assemblyNameSymbols); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | private void UpdateFileFacade(IFileFacade facade, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols) | ||
| 55 | { | ||
| 56 | FileInfo fileInfo = null; | ||
| 57 | try | ||
| 58 | { | ||
| 59 | fileInfo = new FileInfo(facade.SourcePath); | ||
| 60 | } | ||
| 61 | catch (ArgumentException) | ||
| 62 | { | ||
| 63 | this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); | ||
| 64 | return; | ||
| 65 | } | ||
| 66 | catch (PathTooLongException) | ||
| 67 | { | ||
| 68 | this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); | ||
| 69 | return; | ||
| 70 | } | ||
| 71 | catch (NotSupportedException) | ||
| 72 | { | ||
| 73 | this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); | ||
| 74 | return; | ||
| 75 | } | ||
| 76 | |||
| 77 | if (!fileInfo.Exists) | ||
| 78 | { | ||
| 79 | this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath)); | ||
| 80 | return; | ||
| 81 | } | ||
| 82 | |||
| 83 | using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) | ||
| 84 | { | ||
| 85 | if (Int32.MaxValue < fileStream.Length) | ||
| 86 | { | ||
| 87 | throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); | ||
| 88 | } | ||
| 89 | |||
| 90 | facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); | ||
| 91 | } | ||
| 92 | |||
| 93 | string version = null; | ||
| 94 | string language = null; | ||
| 95 | try | ||
| 96 | { | ||
| 97 | Installer.GetFileVersion(fileInfo.FullName, out version, out language); | ||
| 98 | } | ||
| 99 | catch (Win32Exception e) | ||
| 100 | { | ||
| 101 | if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND | ||
| 102 | { | ||
| 103 | throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); | ||
| 104 | } | ||
| 105 | else | ||
| 106 | { | ||
| 107 | throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. | ||
| 112 | if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table | ||
| 113 | { | ||
| 114 | if (!this.OverwriteHash) | ||
| 115 | { | ||
| 116 | // not overwriting hash, so don't do the rest of these options. | ||
| 117 | } | ||
| 118 | else if (null != facade.Version) | ||
| 119 | { | ||
| 120 | // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks | ||
| 121 | // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up. | ||
| 122 | // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing | ||
| 123 | // all the file rows) for a relatively uncommon situation. Let's not do that. | ||
| 124 | // | ||
| 125 | // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version | ||
| 126 | // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. | ||
| 127 | if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) | ||
| 128 | { | ||
| 129 | this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | else | ||
| 133 | { | ||
| 134 | if (null != facade.Language) | ||
| 135 | { | ||
| 136 | this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); | ||
| 137 | } | ||
| 138 | |||
| 139 | int[] hash; | ||
| 140 | try | ||
| 141 | { | ||
| 142 | Installer.GetFileHash(fileInfo.FullName, 0, out hash); | ||
| 143 | } | ||
| 144 | catch (Win32Exception e) | ||
| 145 | { | ||
| 146 | if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND | ||
| 147 | { | ||
| 148 | throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); | ||
| 149 | } | ||
| 150 | else | ||
| 151 | { | ||
| 152 | throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | if (null == facade.Hash) | ||
| 157 | { | ||
| 158 | facade.Hash = this.Section.AddSymbol(new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)); | ||
| 159 | } | ||
| 160 | |||
| 161 | facade.Hash.Options = 0; | ||
| 162 | facade.Hash.HashPart1 = hash[0]; | ||
| 163 | facade.Hash.HashPart2 = hash[1]; | ||
| 164 | facade.Hash.HashPart3 = hash[2]; | ||
| 165 | facade.Hash.HashPart4 = hash[3]; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | else // update the file row with the version and language information. | ||
| 169 | { | ||
| 170 | // If no version was provided by the user, use the version from the file itself. | ||
| 171 | // This is the most common case. | ||
| 172 | if (String.IsNullOrEmpty(facade.Version)) | ||
| 173 | { | ||
| 174 | facade.Version = version; | ||
| 175 | } | ||
| 176 | else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. | ||
| 177 | { | ||
| 178 | // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching | ||
| 179 | // the version value). We didn't find it so, we will override the default version they provided with the actual | ||
| 180 | // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match | ||
| 181 | // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case | ||
| 182 | // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more | ||
| 183 | // CPU intensive search to save on the memory intensive index that wouldn't be used much. | ||
| 184 | // | ||
| 185 | // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. | ||
| 186 | // That's typically even more rare than companion files so again, no index, just search. | ||
| 187 | facade.Version = version; | ||
| 188 | } | ||
| 189 | |||
| 190 | if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language)) | ||
| 191 | { | ||
| 192 | this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); | ||
| 193 | } | ||
| 194 | else // override the default provided by the user (usually nothing) with the actual language from the file itself. | ||
| 195 | { | ||
| 196 | facade.Language = language; | ||
| 197 | } | ||
| 198 | |||
| 199 | // Populate the binder variables for this file information if requested. | ||
| 200 | if (null != this.VariableCache) | ||
| 201 | { | ||
| 202 | if (!String.IsNullOrEmpty(facade.Version)) | ||
| 203 | { | ||
| 204 | var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.Id); | ||
| 205 | this.VariableCache[key] = facade.Version; | ||
| 206 | } | ||
| 207 | |||
| 208 | if (!String.IsNullOrEmpty(facade.Language)) | ||
| 209 | { | ||
| 210 | var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.Id); | ||
| 211 | this.VariableCache[key] = facade.Language; | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | // If this is a CLR assembly, load the assembly and get the assembly name information | ||
| 217 | if (AssemblyType.DotNetAssembly == facade.AssemblyType) | ||
| 218 | { | ||
| 219 | try | ||
| 220 | { | ||
| 221 | var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); | ||
| 222 | |||
| 223 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); | ||
| 224 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "culture", assemblyName.Culture); | ||
| 225 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); | ||
| 226 | |||
| 227 | if (!String.IsNullOrEmpty(assemblyName.Architecture)) | ||
| 228 | { | ||
| 229 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); | ||
| 230 | } | ||
| 231 | // TODO: WiX v3 seemed to do this but not clear it should actually be done. | ||
| 232 | //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) | ||
| 233 | //{ | ||
| 234 | // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); | ||
| 235 | //} | ||
| 236 | |||
| 237 | if (assemblyName.StrongNamedSigned) | ||
| 238 | { | ||
| 239 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); | ||
| 240 | } | ||
| 241 | else if (facade.AssemblyApplicationFileRef == null) | ||
| 242 | { | ||
| 243 | throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); | ||
| 244 | } | ||
| 245 | |||
| 246 | if (!String.IsNullOrEmpty(assemblyName.FileVersion)) | ||
| 247 | { | ||
| 248 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "fileVersion", assemblyName.FileVersion); | ||
| 249 | } | ||
| 250 | |||
| 251 | // add the assembly name to the information cache | ||
| 252 | if (null != this.VariableCache) | ||
| 253 | { | ||
| 254 | this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | catch (WixException e) | ||
| 258 | { | ||
| 259 | this.Messaging.Write(e.Error); | ||
| 260 | } | ||
| 261 | } | ||
| 262 | else if (AssemblyType.Win32Assembly == facade.AssemblyType) | ||
| 263 | { | ||
| 264 | // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through | ||
| 265 | // all files like this. Even though this is a rare case it looks like we might be able to index the | ||
| 266 | // file earlier. | ||
| 267 | var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal)); | ||
| 268 | if (null == fileManifest) | ||
| 269 | { | ||
| 270 | this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef)); | ||
| 271 | } | ||
| 272 | |||
| 273 | try | ||
| 274 | { | ||
| 275 | var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); | ||
| 276 | |||
| 277 | if (!String.IsNullOrEmpty(assemblyName.Name)) | ||
| 278 | { | ||
| 279 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); | ||
| 280 | } | ||
| 281 | |||
| 282 | if (!String.IsNullOrEmpty(assemblyName.Version)) | ||
| 283 | { | ||
| 284 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); | ||
| 285 | } | ||
| 286 | |||
| 287 | if (!String.IsNullOrEmpty(assemblyName.Type)) | ||
| 288 | { | ||
| 289 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "type", assemblyName.Type); | ||
| 290 | } | ||
| 291 | |||
| 292 | if (!String.IsNullOrEmpty(assemblyName.Architecture)) | ||
| 293 | { | ||
| 294 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); | ||
| 295 | } | ||
| 296 | |||
| 297 | if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) | ||
| 298 | { | ||
| 299 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | catch (WixException e) | ||
| 303 | { | ||
| 304 | this.Messaging.Write(e.Error); | ||
| 305 | } | ||
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | /// <summary> | ||
| 310 | /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise | ||
| 311 | /// create a new row. | ||
| 312 | /// </summary> | ||
| 313 | /// <param name="assemblyNameSymbols">MsiAssemblyName table.</param> | ||
| 314 | /// <param name="facade">FileFacade containing the assembly read for the MsiAssemblyName row.</param> | ||
| 315 | /// <param name="name">MsiAssemblyName name.</param> | ||
| 316 | /// <param name="value">MsiAssemblyName value.</param> | ||
| 317 | private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, IFileFacade facade, string name, string value) | ||
| 318 | { | ||
| 319 | // check for null value (this can occur when grabbing the file version from an assembly without one) | ||
| 320 | if (String.IsNullOrEmpty(value)) | ||
| 321 | { | ||
| 322 | this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name)); | ||
| 323 | } | ||
| 324 | else | ||
| 325 | { | ||
| 326 | // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. | ||
| 327 | if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType && | ||
| 328 | String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) && | ||
| 329 | !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase)) | ||
| 330 | { | ||
| 331 | this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value)); | ||
| 332 | } | ||
| 333 | |||
| 334 | // override directly authored value | ||
| 335 | var lookup = String.Concat(facade.ComponentRef, "/", name); | ||
| 336 | if (!assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol)) | ||
| 337 | { | ||
| 338 | assemblyNameSymbol = this.Section.AddSymbol(new MsiAssemblyNameSymbol(facade.SourceLineNumber, new Identifier(AccessModifier.Section, facade.ComponentRef, name)) | ||
| 339 | { | ||
| 340 | ComponentRef = facade.ComponentRef, | ||
| 341 | Name = name, | ||
| 342 | Value = value, | ||
| 343 | }); | ||
| 344 | |||
| 345 | if (null == facade.AssemblyNames) | ||
| 346 | { | ||
| 347 | facade.AssemblyNames = new List<MsiAssemblyNameSymbol>(); | ||
| 348 | } | ||
| 349 | |||
| 350 | facade.AssemblyNames.Add(assemblyNameSymbol); | ||
| 351 | |||
| 352 | assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); | ||
| 353 | } | ||
| 354 | |||
| 355 | assemblyNameSymbol.Value = value; | ||
| 356 | |||
| 357 | if (this.VariableCache != null) | ||
| 358 | { | ||
| 359 | var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); | ||
| 360 | this.VariableCache[key] = value; | ||
| 361 | } | ||
| 362 | } | ||
| 363 | } | ||
| 364 | } | ||
| 365 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs new file mode 100644 index 00000000..66a648cc --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | internal class UpdateFromTextFilesCommand | ||
| 13 | { | ||
| 14 | public UpdateFromTextFilesCommand(IMessaging messaging, IntermediateSection section) | ||
| 15 | { | ||
| 16 | this.Messaging = messaging; | ||
| 17 | this.Section = section; | ||
| 18 | } | ||
| 19 | |||
| 20 | private IMessaging Messaging { get; } | ||
| 21 | |||
| 22 | private IntermediateSection Section { get; } | ||
| 23 | |||
| 24 | public void Execute() | ||
| 25 | { | ||
| 26 | foreach (var bbControl in this.Section.Symbols.OfType<BBControlSymbol>().Where(t => t.SourceFile != null)) | ||
| 27 | { | ||
| 28 | bbControl.Text = this.ReadTextFile(bbControl.SourceLineNumbers, bbControl.SourceFile.Path); | ||
| 29 | } | ||
| 30 | |||
| 31 | foreach (var control in this.Section.Symbols.OfType<ControlSymbol>().Where(t => t.SourceFile != null)) | ||
| 32 | { | ||
| 33 | control.Text = this.ReadTextFile(control.SourceLineNumbers, control.SourceFile.Path); | ||
| 34 | } | ||
| 35 | |||
| 36 | foreach (var customAction in this.Section.Symbols.OfType<CustomActionSymbol>().Where(c => c.ScriptFile != null)) | ||
| 37 | { | ||
| 38 | customAction.Target = this.ReadTextFile(customAction.SourceLineNumbers, customAction.ScriptFile.Path); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | /// <summary> | ||
| 43 | /// Reads a text file and returns the contents. | ||
| 44 | /// </summary> | ||
| 45 | /// <param name="sourceLineNumbers">Source line numbers for row from source.</param> | ||
| 46 | /// <param name="source">Source path to file to read.</param> | ||
| 47 | /// <returns>Text string read from file.</returns> | ||
| 48 | private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source) | ||
| 49 | { | ||
| 50 | try | ||
| 51 | { | ||
| 52 | using (var reader = new StreamReader(source)) | ||
| 53 | { | ||
| 54 | return reader.ReadToEnd(); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | catch (DirectoryNotFoundException e) | ||
| 58 | { | ||
| 59 | this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); | ||
| 60 | } | ||
| 61 | catch (FileNotFoundException e) | ||
| 62 | { | ||
| 63 | this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); | ||
| 64 | } | ||
| 65 | catch (IOException e) | ||
| 66 | { | ||
| 67 | this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); | ||
| 68 | } | ||
| 69 | catch (NotSupportedException) | ||
| 70 | { | ||
| 71 | this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, source)); | ||
| 72 | } | ||
| 73 | |||
| 74 | return null; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs new file mode 100644 index 00000000..affec09f --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs | |||
| @@ -0,0 +1,109 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.Linq; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Symbols; | ||
| 9 | using WixToolset.Extensibility.Data; | ||
| 10 | |||
| 11 | internal class UpdateMediaSequencesCommand | ||
| 12 | { | ||
| 13 | public UpdateMediaSequencesCommand(IntermediateSection section, IEnumerable<IFileFacade> fileFacades) | ||
| 14 | { | ||
| 15 | this.Section = section; | ||
| 16 | this.FileFacades = fileFacades; | ||
| 17 | } | ||
| 18 | |||
| 19 | private IntermediateSection Section { get; } | ||
| 20 | |||
| 21 | private IEnumerable<IFileFacade> FileFacades { get; } | ||
| 22 | |||
| 23 | public void Execute() | ||
| 24 | { | ||
| 25 | var mediaRows = this.Section.Symbols.OfType<MediaSymbol>().ToDictionary(t => t.DiskId); | ||
| 26 | |||
| 27 | // Calculate sequence numbers and media disk id layout for all file media information objects. | ||
| 28 | if (SectionType.Module == this.Section.Type) | ||
| 29 | { | ||
| 30 | var lastSequence = 0; | ||
| 31 | |||
| 32 | foreach (var facade in this.FileFacades) | ||
| 33 | { | ||
| 34 | facade.Sequence = ++lastSequence; | ||
| 35 | } | ||
| 36 | } | ||
| 37 | else | ||
| 38 | { | ||
| 39 | var lastSequence = 0; | ||
| 40 | MediaSymbol mediaSymbol = null; | ||
| 41 | var patchGroups = new Dictionary<int, List<IFileFacade>>(); | ||
| 42 | |||
| 43 | // Sequence the non-patch-added files. | ||
| 44 | foreach (var facade in this.FileFacades) | ||
| 45 | { | ||
| 46 | if (null == mediaSymbol) | ||
| 47 | { | ||
| 48 | mediaSymbol = mediaRows[facade.DiskId]; | ||
| 49 | if (SectionType.Patch == this.Section.Type) | ||
| 50 | { | ||
| 51 | // patch Media cannot start at zero | ||
| 52 | lastSequence = mediaSymbol.LastSequence ?? 1; | ||
| 53 | } | ||
| 54 | } | ||
| 55 | else if (mediaSymbol.DiskId != facade.DiskId) | ||
| 56 | { | ||
| 57 | mediaSymbol.LastSequence = lastSequence; | ||
| 58 | mediaSymbol = mediaRows[facade.DiskId]; | ||
| 59 | } | ||
| 60 | |||
| 61 | if (facade.PatchGroup.HasValue) | ||
| 62 | { | ||
| 63 | if (patchGroups.TryGetValue(facade.PatchGroup.Value, out var patchGroup)) | ||
| 64 | { | ||
| 65 | patchGroup = new List<IFileFacade>(); | ||
| 66 | patchGroups.Add(facade.PatchGroup.Value, patchGroup); | ||
| 67 | } | ||
| 68 | |||
| 69 | patchGroup.Add(facade); | ||
| 70 | } | ||
| 71 | else if (!facade.FromModule) | ||
| 72 | { | ||
| 73 | facade.Sequence = ++lastSequence; | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | if (null != mediaSymbol) | ||
| 78 | { | ||
| 79 | mediaSymbol.LastSequence = lastSequence; | ||
| 80 | mediaSymbol = null; | ||
| 81 | } | ||
| 82 | |||
| 83 | // Sequence the patch-added files. | ||
| 84 | foreach (var patchGroup in patchGroups.Values) | ||
| 85 | { | ||
| 86 | foreach (var facade in patchGroup) | ||
| 87 | { | ||
| 88 | if (null == mediaSymbol) | ||
| 89 | { | ||
| 90 | mediaSymbol = mediaRows[facade.DiskId]; | ||
| 91 | } | ||
| 92 | else if (mediaSymbol.DiskId != facade.DiskId) | ||
| 93 | { | ||
| 94 | mediaSymbol.LastSequence = lastSequence; | ||
| 95 | mediaSymbol = mediaRows[facade.DiskId]; | ||
| 96 | } | ||
| 97 | |||
| 98 | facade.Sequence = ++lastSequence; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | if (null != mediaSymbol) | ||
| 103 | { | ||
| 104 | mediaSymbol.LastSequence = lastSequence; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs new file mode 100644 index 00000000..981fa0a4 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs | |||
| @@ -0,0 +1,451 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Data.WindowsInstaller; | ||
| 11 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 12 | using WixToolset.Extensibility.Data; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | internal class UpdateTransformsWithFileFacades | ||
| 16 | { | ||
| 17 | public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable<SubStorage> subStorages, TableDefinitionCollection tableDefinitions, IEnumerable<IFileFacade> fileFacades) | ||
| 18 | { | ||
| 19 | this.Messaging = messaging; | ||
| 20 | this.Output = output; | ||
| 21 | this.SubStorages = subStorages; | ||
| 22 | this.TableDefinitions = tableDefinitions; | ||
| 23 | this.FileFacades = fileFacades; | ||
| 24 | } | ||
| 25 | |||
| 26 | private IMessaging Messaging { get; } | ||
| 27 | |||
| 28 | private WindowsInstallerData Output { get; } | ||
| 29 | |||
| 30 | private IEnumerable<SubStorage> SubStorages { get; } | ||
| 31 | |||
| 32 | private TableDefinitionCollection TableDefinitions { get; } | ||
| 33 | |||
| 34 | private IEnumerable<IFileFacade> FileFacades { get; } | ||
| 35 | |||
| 36 | public void Execute() | ||
| 37 | { | ||
| 38 | var fileFacadesByDiskId = new Dictionary<int, Dictionary<string, IFileFacade>>(); | ||
| 39 | |||
| 40 | // Index patch file facades by diskId+fileId. | ||
| 41 | foreach (var facade in this.FileFacades) | ||
| 42 | { | ||
| 43 | if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades)) | ||
| 44 | { | ||
| 45 | mediaFacades = new Dictionary<string, IFileFacade>(); | ||
| 46 | fileFacadesByDiskId.Add(facade.DiskId, mediaFacades); | ||
| 47 | } | ||
| 48 | |||
| 49 | mediaFacades.Add(facade.Id, facade); | ||
| 50 | } | ||
| 51 | |||
| 52 | var patchMediaRows = new RowDictionary<MediaRow>(this.Output.Tables["Media"]); | ||
| 53 | |||
| 54 | // Index paired transforms by name without the "#" prefix. | ||
| 55 | var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); | ||
| 56 | |||
| 57 | // Copy File bind data into substorages | ||
| 58 | foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) | ||
| 59 | { | ||
| 60 | var mainTransform = substorage.Data; | ||
| 61 | |||
| 62 | var mainMsiFileHashIndex = new RowDictionary<Row>(mainTransform.Tables["MsiFileHash"]); | ||
| 63 | |||
| 64 | var pairedTransform = pairedTransforms["#" + substorage.Name]; | ||
| 65 | |||
| 66 | // Copy Media.LastSequence. | ||
| 67 | var pairedMediaTable = pairedTransform.Tables["Media"]; | ||
| 68 | foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) | ||
| 69 | { | ||
| 70 | var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); | ||
| 71 | pairedMediaRow.LastSequence = patchMediaRow.LastSequence; | ||
| 72 | } | ||
| 73 | |||
| 74 | // Validate file row changes for keypath-related issues | ||
| 75 | this.ValidateFileRowChanges(mainTransform); | ||
| 76 | |||
| 77 | // Index File table of pairedTransform | ||
| 78 | var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]); | ||
| 79 | |||
| 80 | var mainFileTable = mainTransform.Tables["File"]; | ||
| 81 | if (null != mainFileTable) | ||
| 82 | { | ||
| 83 | // Remove the MsiFileHash table because it will be updated later with the final file hash for each file | ||
| 84 | mainTransform.Tables.Remove("MsiFileHash"); | ||
| 85 | |||
| 86 | foreach (FileRow mainFileRow in mainFileTable.Rows) | ||
| 87 | { | ||
| 88 | if (RowOperation.Delete == mainFileRow.Operation) | ||
| 89 | { | ||
| 90 | continue; | ||
| 91 | } | ||
| 92 | else if (RowOperation.None == mainFileRow.Operation) | ||
| 93 | { | ||
| 94 | continue; | ||
| 95 | } | ||
| 96 | |||
| 97 | // Index patch files by diskId+fileId | ||
| 98 | if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades)) | ||
| 99 | { | ||
| 100 | mediaFacades = new Dictionary<string, IFileFacade>(); | ||
| 101 | fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades); | ||
| 102 | } | ||
| 103 | |||
| 104 | // copy data from the patch back to the transform | ||
| 105 | if (mediaFacades.TryGetValue(mainFileRow.File, out var facade)) | ||
| 106 | { | ||
| 107 | var patchFileRow = facade.GetFileRow(); | ||
| 108 | var pairedFileRow = pairedFileRows.Get(mainFileRow.File); | ||
| 109 | |||
| 110 | for (var i = 0; i < patchFileRow.Fields.Length; i++) | ||
| 111 | { | ||
| 112 | var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty; | ||
| 113 | var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty; | ||
| 114 | |||
| 115 | if (1 == i) | ||
| 116 | { | ||
| 117 | // File.Component_ changes should not come from the shared file rows | ||
| 118 | // that contain the file information as each individual transform might | ||
| 119 | // have different changes (or no changes at all). | ||
| 120 | } | ||
| 121 | else if (6 == i) // File.Attributes should not changed for binary deltas | ||
| 122 | { | ||
| 123 | #if TODO_PATCHING_DELTA | ||
| 124 | if (null != patchFileRow.Patch) | ||
| 125 | { | ||
| 126 | // File.Attribute should not change for binary deltas | ||
| 127 | pairedFileRow.Attributes = mainFileRow.Attributes; | ||
| 128 | mainFileRow.Fields[i].Modified = false; | ||
| 129 | } | ||
| 130 | #endif | ||
| 131 | } | ||
| 132 | else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform | ||
| 133 | { | ||
| 134 | // file sequence is updated in Patch table instead of File table for delta patches | ||
| 135 | #if TODO_PATCHING_DELTA | ||
| 136 | if (null != patchFileRow.Patch) | ||
| 137 | { | ||
| 138 | pairedFileRow.Fields[i].Modified = false; | ||
| 139 | } | ||
| 140 | else | ||
| 141 | #endif | ||
| 142 | { | ||
| 143 | pairedFileRow[i] = patchFileRow[i]; | ||
| 144 | pairedFileRow.Fields[i].Modified = true; | ||
| 145 | } | ||
| 146 | mainFileRow.Fields[i].Modified = false; | ||
| 147 | } | ||
| 148 | else if (patchValue != mainValue) | ||
| 149 | { | ||
| 150 | mainFileRow[i] = patchFileRow[i]; | ||
| 151 | mainFileRow.Fields[i].Modified = true; | ||
| 152 | if (mainFileRow.Operation == RowOperation.None) | ||
| 153 | { | ||
| 154 | mainFileRow.Operation = RowOperation.Modify; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | // Copy MsiFileHash row for this File. | ||
| 160 | if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) | ||
| 161 | { | ||
| 162 | //patchHashRow = patchFileRow.Hash; | ||
| 163 | throw new NotImplementedException(); | ||
| 164 | } | ||
| 165 | |||
| 166 | if (null != patchHashRow) | ||
| 167 | { | ||
| 168 | var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); | ||
| 169 | var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 170 | for (var i = 0; i < patchHashRow.Fields.Length; i++) | ||
| 171 | { | ||
| 172 | mainHashRow[i] = patchHashRow[i]; | ||
| 173 | if (i > 1) | ||
| 174 | { | ||
| 175 | // assume all hash fields have been modified | ||
| 176 | mainHashRow.Fields[i].Modified = true; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | // assume the MsiFileHash operation follows the File one | ||
| 181 | mainHashRow.Operation = mainFileRow.Operation; | ||
| 182 | } | ||
| 183 | |||
| 184 | // copy MsiAssemblyName rows for this File | ||
| 185 | #if TODO_PATCHING | ||
| 186 | List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames; | ||
| 187 | if (null != patchAssemblyNameRows) | ||
| 188 | { | ||
| 189 | var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); | ||
| 190 | foreach (var patchAssemblyNameRow in patchAssemblyNameRows) | ||
| 191 | { | ||
| 192 | // Copy if there isn't an identical modified/added row already in the transform. | ||
| 193 | var foundMatchingModifiedRow = false; | ||
| 194 | foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) | ||
| 195 | { | ||
| 196 | if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) | ||
| 197 | { | ||
| 198 | foundMatchingModifiedRow = true; | ||
| 199 | break; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | if (!foundMatchingModifiedRow) | ||
| 204 | { | ||
| 205 | var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 206 | for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) | ||
| 207 | { | ||
| 208 | mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; | ||
| 209 | } | ||
| 210 | |||
| 211 | // assume value field has been modified | ||
| 212 | mainAssemblyNameRow.Fields[2].Modified = true; | ||
| 213 | mainAssemblyNameRow.Operation = mainFileRow.Operation; | ||
| 214 | } | ||
| 215 | } | ||
| 216 | } | ||
| 217 | #endif | ||
| 218 | |||
| 219 | // Add patch header for this file | ||
| 220 | #if TODO_PATCHING_DELTA | ||
| 221 | if (null != patchFileRow.Patch) | ||
| 222 | { | ||
| 223 | // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. | ||
| 224 | this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); | ||
| 225 | this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); | ||
| 226 | |||
| 227 | // Add to Patch table | ||
| 228 | var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); | ||
| 229 | if (0 == patchTable.Rows.Count) | ||
| 230 | { | ||
| 231 | patchTable.Operation = TableOperation.Add; | ||
| 232 | } | ||
| 233 | |||
| 234 | var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 235 | patchRow[0] = patchFileRow.File; | ||
| 236 | patchRow[1] = patchFileRow.Sequence; | ||
| 237 | |||
| 238 | var patchFile = new FileInfo(patchFileRow.Source); | ||
| 239 | patchRow[2] = (int)patchFile.Length; | ||
| 240 | patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; | ||
| 241 | |||
| 242 | var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; | ||
| 243 | if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) | ||
| 244 | { | ||
| 245 | streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); | ||
| 246 | |||
| 247 | var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); | ||
| 248 | if (0 == patchHeadersTable.Rows.Count) | ||
| 249 | { | ||
| 250 | patchHeadersTable.Operation = TableOperation.Add; | ||
| 251 | } | ||
| 252 | |||
| 253 | var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); | ||
| 254 | patchHeadersRow[0] = streamName; | ||
| 255 | patchHeadersRow[1] = patchFileRow.Patch; | ||
| 256 | patchRow[5] = streamName; | ||
| 257 | patchHeadersRow.Operation = RowOperation.Add; | ||
| 258 | } | ||
| 259 | else | ||
| 260 | { | ||
| 261 | patchRow[4] = patchFileRow.Patch; | ||
| 262 | } | ||
| 263 | patchRow.Operation = RowOperation.Add; | ||
| 264 | } | ||
| 265 | #endif | ||
| 266 | } | ||
| 267 | else | ||
| 268 | { | ||
| 269 | // TODO: throw because all transform rows should have made it into the patch | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | this.Output.Tables.Remove("Media"); | ||
| 275 | this.Output.Tables.Remove("File"); | ||
| 276 | this.Output.Tables.Remove("MsiFileHash"); | ||
| 277 | this.Output.Tables.Remove("MsiAssemblyName"); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | /// <summary> | ||
| 282 | /// Adds the PatchFiles action to the sequence table if it does not already exist. | ||
| 283 | /// </summary> | ||
| 284 | /// <param name="table">The sequence table to check or modify.</param> | ||
| 285 | /// <param name="mainTransform">The primary authoring transform.</param> | ||
| 286 | /// <param name="pairedTransform">The secondary patch transform.</param> | ||
| 287 | /// <param name="mainFileRow">The file row that contains information about the patched file.</param> | ||
| 288 | private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow) | ||
| 289 | { | ||
| 290 | var tableName = table.ToString(); | ||
| 291 | |||
| 292 | // Find/add PatchFiles action (also determine sequence for it). | ||
| 293 | // Search mainTransform first, then pairedTransform (pairedTransform overrides). | ||
| 294 | var hasPatchFilesAction = false; | ||
| 295 | var installFilesSequence = 0; | ||
| 296 | var duplicateFilesSequence = 0; | ||
| 297 | |||
| 298 | TestSequenceTableForPatchFilesAction( | ||
| 299 | mainTransform.Tables[tableName], | ||
| 300 | ref hasPatchFilesAction, | ||
| 301 | ref installFilesSequence, | ||
| 302 | ref duplicateFilesSequence); | ||
| 303 | TestSequenceTableForPatchFilesAction( | ||
| 304 | pairedTransform.Tables[tableName], | ||
| 305 | ref hasPatchFilesAction, | ||
| 306 | ref installFilesSequence, | ||
| 307 | ref duplicateFilesSequence); | ||
| 308 | if (!hasPatchFilesAction) | ||
| 309 | { | ||
| 310 | WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionSymbol); | ||
| 311 | |||
| 312 | var sequence = patchFilesActionSymbol.Sequence; | ||
| 313 | |||
| 314 | // Test for default sequence value's appropriateness | ||
| 315 | if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence)) | ||
| 316 | { | ||
| 317 | if (0 != duplicateFilesSequence) | ||
| 318 | { | ||
| 319 | if (duplicateFilesSequence < installFilesSequence) | ||
| 320 | { | ||
| 321 | throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); | ||
| 322 | } | ||
| 323 | else | ||
| 324 | { | ||
| 325 | sequence = (duplicateFilesSequence + installFilesSequence) / 2; | ||
| 326 | if (installFilesSequence == sequence || duplicateFilesSequence == sequence) | ||
| 327 | { | ||
| 328 | throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | else | ||
| 333 | { | ||
| 334 | sequence = installFilesSequence + 1; | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); | ||
| 339 | if (0 == sequenceTable.Rows.Count) | ||
| 340 | { | ||
| 341 | sequenceTable.Operation = TableOperation.Add; | ||
| 342 | } | ||
| 343 | |||
| 344 | var patchAction = sequenceTable.CreateRow(null); | ||
| 345 | patchAction[0] = patchFilesActionSymbol.Action; | ||
| 346 | patchAction[1] = patchFilesActionSymbol.Condition; | ||
| 347 | patchAction[2] = sequence; | ||
| 348 | patchAction.Operation = RowOperation.Add; | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | /// <summary> | ||
| 353 | /// Tests sequence table for PatchFiles and associated actions | ||
| 354 | /// </summary> | ||
| 355 | /// <param name="sequenceTable">The table to test.</param> | ||
| 356 | /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param> | ||
| 357 | /// <param name="installFilesSequence">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param> | ||
| 358 | /// <param name="duplicateFilesSequence">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param> | ||
| 359 | private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence) | ||
| 360 | { | ||
| 361 | if (null != sequenceTable) | ||
| 362 | { | ||
| 363 | foreach (var row in sequenceTable.Rows) | ||
| 364 | { | ||
| 365 | var actionName = row.FieldAsString(0); | ||
| 366 | switch (actionName) | ||
| 367 | { | ||
| 368 | case "PatchFiles": | ||
| 369 | hasPatchFilesAction = true; | ||
| 370 | break; | ||
| 371 | |||
| 372 | case "InstallFiles": | ||
| 373 | installFilesSequence = row.FieldAsInteger(2); | ||
| 374 | break; | ||
| 375 | |||
| 376 | case "DuplicateFiles": | ||
| 377 | duplicateFilesSequence = row.FieldAsInteger(2); | ||
| 378 | break; | ||
| 379 | } | ||
| 380 | } | ||
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | /// <summary> | ||
| 385 | /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. | ||
| 386 | /// </summary> | ||
| 387 | /// <param name="transform">The output to validate.</param> | ||
| 388 | private void ValidateFileRowChanges(WindowsInstallerData transform) | ||
| 389 | { | ||
| 390 | var componentTable = transform.Tables["Component"]; | ||
| 391 | var fileTable = transform.Tables["File"]; | ||
| 392 | |||
| 393 | // There's no sense validating keypaths if the transform has no component or file table | ||
| 394 | if (componentTable == null || fileTable == null) | ||
| 395 | { | ||
| 396 | return; | ||
| 397 | } | ||
| 398 | |||
| 399 | var componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count); | ||
| 400 | |||
| 401 | // Index the Component table for non-directory & non-registry key paths. | ||
| 402 | foreach (var row in componentTable.Rows) | ||
| 403 | { | ||
| 404 | var keyPath = row.FieldAsString(5); | ||
| 405 | if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) | ||
| 406 | { | ||
| 407 | componentKeyPath.Add(row.FieldAsString(0), keyPath); | ||
| 408 | } | ||
| 409 | } | ||
| 410 | |||
| 411 | var componentWithChangedKeyPath = new Dictionary<string, string>(); | ||
| 412 | var componentWithNonKeyPathChanged = new Dictionary<string, string>(); | ||
| 413 | // Verify changes in the file table, now that file diffing has occurred | ||
| 414 | foreach (FileRow row in fileTable.Rows) | ||
| 415 | { | ||
| 416 | if (RowOperation.Modify != row.Operation) | ||
| 417 | { | ||
| 418 | continue; | ||
| 419 | } | ||
| 420 | |||
| 421 | var fileId = row.FieldAsString(0); | ||
| 422 | var componentId = row.FieldAsString(1); | ||
| 423 | |||
| 424 | // If this file is the keypath of a component | ||
| 425 | if (componentKeyPath.ContainsValue(fileId)) | ||
| 426 | { | ||
| 427 | if (!componentWithChangedKeyPath.ContainsKey(componentId)) | ||
| 428 | { | ||
| 429 | componentWithChangedKeyPath.Add(componentId, fileId); | ||
| 430 | } | ||
| 431 | } | ||
| 432 | else | ||
| 433 | { | ||
| 434 | if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) | ||
| 435 | { | ||
| 436 | componentWithNonKeyPathChanged.Add(componentId, fileId); | ||
| 437 | } | ||
| 438 | } | ||
| 439 | } | ||
| 440 | |||
| 441 | foreach (var componentFile in componentWithNonKeyPathChanged) | ||
| 442 | { | ||
| 443 | // Make sure all changes to non keypath files also had a change in the keypath. | ||
| 444 | if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath)) | ||
| 445 | { | ||
| 446 | this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath)); | ||
| 447 | } | ||
| 448 | } | ||
| 449 | } | ||
| 450 | } | ||
| 451 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs new file mode 100644 index 00000000..cf1e21c2 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs | |||
| @@ -0,0 +1,187 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using WixToolset.Core.Native; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.WindowsInstaller; | ||
| 13 | using WixToolset.Extensibility.Data; | ||
| 14 | using WixToolset.Extensibility.Services; | ||
| 15 | |||
| 16 | internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback | ||
| 17 | { | ||
| 18 | // Set of ICEs that have equivalent-or-better checks in WiX. | ||
| 19 | private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" }; | ||
| 20 | |||
| 21 | public ValidateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable<string> cubeFiles, IEnumerable<string> ices, IEnumerable<string> suppressedIces) | ||
| 22 | { | ||
| 23 | this.Messaging = messaging; | ||
| 24 | this.BackendHelper = backendHelper; | ||
| 25 | this.Data = data; | ||
| 26 | this.OutputPath = outputPath; | ||
| 27 | this.CubeFiles = cubeFiles; | ||
| 28 | this.Ices = ices; | ||
| 29 | this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces); | ||
| 30 | |||
| 31 | this.IntermediateFolder = intermediateFolder; | ||
| 32 | this.OutputSourceLineNumber = new SourceLineNumber(outputPath); | ||
| 33 | } | ||
| 34 | |||
| 35 | public IEnumerable<ITrackedFile> TrackedFiles { get; private set; } | ||
| 36 | |||
| 37 | /// <summary> | ||
| 38 | /// Encountered error implementation for <see cref="IWindowsInstallerValidatorCallback"/>. | ||
| 39 | /// </summary> | ||
| 40 | public bool EncounteredError => this.Messaging.EncounteredError; | ||
| 41 | |||
| 42 | private IMessaging Messaging { get; } | ||
| 43 | |||
| 44 | private IBackendHelper BackendHelper { get; } | ||
| 45 | |||
| 46 | private WindowsInstallerData Data { get; } | ||
| 47 | |||
| 48 | private string OutputPath { get; } | ||
| 49 | |||
| 50 | private IEnumerable<string> CubeFiles { get; } | ||
| 51 | |||
| 52 | private IEnumerable<string> Ices { get; } | ||
| 53 | |||
| 54 | private IEnumerable<string> SuppressedIces { get; } | ||
| 55 | |||
| 56 | private string IntermediateFolder { get; } | ||
| 57 | |||
| 58 | /// <summary> | ||
| 59 | /// Fallback when an exact source line number cannot be calculated for a validation error. | ||
| 60 | /// </summary> | ||
| 61 | private SourceLineNumber OutputSourceLineNumber { get; set; } | ||
| 62 | |||
| 63 | private Dictionary<string, SourceLineNumber> SourceLineNumbersByTablePrimaryKey { get; set; } | ||
| 64 | |||
| 65 | public void Execute() | ||
| 66 | { | ||
| 67 | var trackedFiles = new List<ITrackedFile>(); | ||
| 68 | var stopwatch = Stopwatch.StartNew(); | ||
| 69 | |||
| 70 | this.Messaging.Write(VerboseMessages.ValidatingDatabase()); | ||
| 71 | |||
| 72 | // Ensure the temporary files can be created the working folder. | ||
| 73 | var workingFolder = Path.Combine(this.IntermediateFolder, "_validate"); | ||
| 74 | Directory.CreateDirectory(workingFolder); | ||
| 75 | |||
| 76 | // Copy the database to a temporary location so it can be manipulated. | ||
| 77 | // Ensure it is not read-only. | ||
| 78 | var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath)); | ||
| 79 | FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false); | ||
| 80 | |||
| 81 | var trackWorkingDatabase = this.BackendHelper.TrackFile(workingDatabasePath, TrackedFileType.Temporary); | ||
| 82 | trackedFiles.Add(trackWorkingDatabase); | ||
| 83 | |||
| 84 | var attributes = File.GetAttributes(workingDatabasePath); | ||
| 85 | File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly); | ||
| 86 | |||
| 87 | var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces); | ||
| 88 | validator.Execute(); | ||
| 89 | |||
| 90 | stopwatch.Stop(); | ||
| 91 | this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); | ||
| 92 | |||
| 93 | |||
| 94 | this.TrackedFiles = trackedFiles; | ||
| 95 | } | ||
| 96 | |||
| 97 | private void LogValidationMessage(ValidationMessage message) | ||
| 98 | { | ||
| 99 | var messageSourceLineNumbers = this.OutputSourceLineNumber; | ||
| 100 | if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null) | ||
| 101 | { | ||
| 102 | messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys); | ||
| 103 | } | ||
| 104 | |||
| 105 | switch (message.Type) | ||
| 106 | { | ||
| 107 | case ValidationMessageType.InternalFailure: | ||
| 108 | case ValidationMessageType.Error: | ||
| 109 | this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description)); | ||
| 110 | break; | ||
| 111 | case ValidationMessageType.Warning: | ||
| 112 | this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description)); | ||
| 113 | break; | ||
| 114 | case ValidationMessageType.Info: | ||
| 115 | this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description)); | ||
| 116 | break; | ||
| 117 | default: | ||
| 118 | throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString())); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | /// <summary> | ||
| 123 | /// Validation blocked by other installation operation for <see cref="IWindowsInstallerValidatorCallback"/>. | ||
| 124 | /// </summary> | ||
| 125 | public void ValidationBlocked() | ||
| 126 | { | ||
| 127 | this.Messaging.Write(VerboseMessages.ValidationSerialized()); | ||
| 128 | } | ||
| 129 | |||
| 130 | /// <summary> | ||
| 131 | /// Validation message implementation for <see cref="IWindowsInstallerValidatorCallback"/>. | ||
| 132 | /// </summary> | ||
| 133 | public bool ValidationMessage(ValidationMessage message) | ||
| 134 | { | ||
| 135 | this.LogValidationMessage(message); | ||
| 136 | return true; | ||
| 137 | } | ||
| 138 | |||
| 139 | /// <summary> | ||
| 140 | /// Gets the source line information (if available) for a row by its table name and primary key. | ||
| 141 | /// </summary> | ||
| 142 | /// <param name="tableName">The table name of the row.</param> | ||
| 143 | /// <param name="primaryKeys">The primary keys of the row.</param> | ||
| 144 | /// <returns>The source line number information if found; null otherwise.</returns> | ||
| 145 | private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable<string> primaryKeys) | ||
| 146 | { | ||
| 147 | // Source line information only exists if an output file was supplied | ||
| 148 | if (this.Data == null) | ||
| 149 | { | ||
| 150 | // Use the file name as the source line information. | ||
| 151 | return this.OutputSourceLineNumber; | ||
| 152 | } | ||
| 153 | |||
| 154 | // Index the source line information if it hasn't been indexed already. | ||
| 155 | if (this.SourceLineNumbersByTablePrimaryKey == null) | ||
| 156 | { | ||
| 157 | this.SourceLineNumbersByTablePrimaryKey = new Dictionary<string, SourceLineNumber>(); | ||
| 158 | |||
| 159 | // Index each real table | ||
| 160 | foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) | ||
| 161 | { | ||
| 162 | // Index each row that contain source line information | ||
| 163 | foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null)) | ||
| 164 | { | ||
| 165 | // Index the row using its table name and primary key | ||
| 166 | var primaryKey = row.GetPrimaryKey(';'); | ||
| 167 | |||
| 168 | if (!String.IsNullOrEmpty(primaryKey)) | ||
| 169 | { | ||
| 170 | try | ||
| 171 | { | ||
| 172 | var key = String.Concat(table.Name, ":", primaryKey); | ||
| 173 | this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers); | ||
| 174 | } | ||
| 175 | catch (ArgumentException) | ||
| 176 | { | ||
| 177 | this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null; | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs new file mode 100644 index 00000000..aeda4443 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Decompile | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.ComponentModel; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using WixToolset.Core.Native.Msi; | ||
| 11 | using WixToolset.Core.WindowsInstaller.Unbind; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.WindowsInstaller; | ||
| 14 | using WixToolset.Extensibility; | ||
| 15 | using WixToolset.Extensibility.Data; | ||
| 16 | using WixToolset.Extensibility.Services; | ||
| 17 | |||
| 18 | internal class DecompileMsiOrMsmCommand | ||
| 19 | { | ||
| 20 | public DecompileMsiOrMsmCommand(IDecompileContext context, IEnumerable<IWindowsInstallerBackendDecompilerExtension> backendExtensions) | ||
| 21 | { | ||
| 22 | this.Context = context; | ||
| 23 | this.Extensions = backendExtensions; | ||
| 24 | this.Messaging = context.ServiceProvider.GetService<IMessaging>(); | ||
| 25 | } | ||
| 26 | |||
| 27 | private IDecompileContext Context { get; } | ||
| 28 | |||
| 29 | private IEnumerable<IWindowsInstallerBackendDecompilerExtension> Extensions { get; } | ||
| 30 | |||
| 31 | private IMessaging Messaging { get; } | ||
| 32 | |||
| 33 | public IDecompileResult Execute() | ||
| 34 | { | ||
| 35 | var result = this.Context.ServiceProvider.GetService<IDecompileResult>(); | ||
| 36 | |||
| 37 | try | ||
| 38 | { | ||
| 39 | using (var database = new Database(this.Context.DecompilePath, OpenDatabase.ReadOnly)) | ||
| 40 | { | ||
| 41 | // Delete the directory and its files to prevent cab extraction failure due to an existing file. | ||
| 42 | if (Directory.Exists(this.Context.ExtractFolder)) | ||
| 43 | { | ||
| 44 | Directory.Delete(this.Context.ExtractFolder, true); | ||
| 45 | } | ||
| 46 | |||
| 47 | var backendHelper = this.Context.ServiceProvider.GetService<IBackendHelper>(); | ||
| 48 | |||
| 49 | var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, database, this.Context.DecompilePath, this.Context.DecompileType, this.Context.ExtractFolder, this.Context.IntermediateFolder, this.Context.IsAdminImage, suppressDemodularization: false, skipSummaryInfo: false); | ||
| 50 | var output = unbindCommand.Execute(); | ||
| 51 | var extractedFilePaths = new List<string>(unbindCommand.ExportedFiles); | ||
| 52 | |||
| 53 | var decompiler = new Decompiler(this.Messaging, backendHelper, this.Extensions, this.Context.BaseSourcePath, this.Context.SuppressCustomTables, this.Context.SuppressDroppingEmptyTables, this.Context.SuppressUI, this.Context.TreatProductAsModule); | ||
| 54 | result.Document = decompiler.Decompile(output); | ||
| 55 | |||
| 56 | result.Platform = GetPlatformFromOutput(output); | ||
| 57 | |||
| 58 | // extract the files from the cabinets | ||
| 59 | if (!String.IsNullOrEmpty(this.Context.ExtractFolder) && !this.Context.SuppressExtractCabinets) | ||
| 60 | { | ||
| 61 | var fileDirectory = String.IsNullOrEmpty(this.Context.CabinetExtractFolder) ? Path.Combine(this.Context.ExtractFolder, "File") : this.Context.CabinetExtractFolder; | ||
| 62 | |||
| 63 | var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.DecompilePath, fileDirectory, this.Context.IntermediateFolder, this.Context.TreatProductAsModule); | ||
| 64 | extractCommand.Execute(); | ||
| 65 | |||
| 66 | extractedFilePaths.AddRange(extractCommand.ExtractedFiles); | ||
| 67 | result.ExtractedFilePaths = extractedFilePaths; | ||
| 68 | } | ||
| 69 | else | ||
| 70 | { | ||
| 71 | result.ExtractedFilePaths = new string[0]; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | catch (Win32Exception e) | ||
| 76 | { | ||
| 77 | if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED | ||
| 78 | { | ||
| 79 | throw new WixException(ErrorMessages.OpenDatabaseFailed(this.Context.DecompilePath)); | ||
| 80 | } | ||
| 81 | |||
| 82 | throw; | ||
| 83 | } | ||
| 84 | |||
| 85 | return result; | ||
| 86 | } | ||
| 87 | |||
| 88 | private static Platform? GetPlatformFromOutput(WindowsInstallerData output) | ||
| 89 | { | ||
| 90 | var template = output.Tables["_SummaryInformation"]?.Rows.SingleOrDefault(row => row.FieldAsInteger(0) == 7)?.FieldAsString(1); | ||
| 91 | |||
| 92 | return Decompiler.GetPlatformFromTemplateSummaryInformation(template?.Split(';')); | ||
| 93 | |||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs new file mode 100644 index 00000000..0b45a8b3 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs | |||
| @@ -0,0 +1,7596 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Decompile | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Text; | ||
| 11 | using System.Text.RegularExpressions; | ||
| 12 | using System.Xml.Linq; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Symbols; | ||
| 15 | using WixToolset.Data.WindowsInstaller; | ||
| 16 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 17 | using WixToolset.Extensibility; | ||
| 18 | using WixToolset.Extensibility.Services; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Decompiles an msi database into WiX source. | ||
| 22 | /// </summary> | ||
| 23 | internal class Decompiler | ||
| 24 | { | ||
| 25 | private static readonly Regex NullSplitter = new Regex(@"\[~]"); | ||
| 26 | |||
| 27 | // NameToBit arrays | ||
| 28 | private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; | ||
| 29 | private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; | ||
| 30 | private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; | ||
| 31 | private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; | ||
| 32 | private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; | ||
| 33 | private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; | ||
| 34 | private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; | ||
| 35 | private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; | ||
| 36 | private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; | ||
| 37 | private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; | ||
| 38 | private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; | ||
| 39 | private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; | ||
| 40 | private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; | ||
| 41 | private XElement uiElement; | ||
| 42 | |||
| 43 | /// <summary> | ||
| 44 | /// Creates a new decompiler object with a default set of table definitions. | ||
| 45 | /// </summary> | ||
| 46 | public Decompiler(IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IWindowsInstallerBackendDecompilerExtension> extensions, string baseSourcePath, bool suppressCustomTables, bool suppressDroppingEmptyTables, bool suppressUI, bool treatProductAsModule) | ||
| 47 | { | ||
| 48 | this.Messaging = messaging; | ||
| 49 | this.BackendHelper = backendHelper; | ||
| 50 | this.Extensions = extensions; | ||
| 51 | this.BaseSourcePath = baseSourcePath ?? "SourceDir"; | ||
| 52 | this.SuppressCustomTables = suppressCustomTables; | ||
| 53 | this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables; | ||
| 54 | this.SuppressUI = suppressUI; | ||
| 55 | this.TreatProductAsModule = treatProductAsModule; | ||
| 56 | |||
| 57 | this.ExtensionsByTableName = new Dictionary<string, IWindowsInstallerBackendDecompilerExtension>(); | ||
| 58 | this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id); | ||
| 59 | |||
| 60 | this.TableDefinitions = new TableDefinitionCollection(); | ||
| 61 | } | ||
| 62 | |||
| 63 | private IMessaging Messaging { get; } | ||
| 64 | |||
| 65 | private IBackendHelper BackendHelper { get; } | ||
| 66 | |||
| 67 | private IEnumerable<IWindowsInstallerBackendDecompilerExtension> Extensions { get; } | ||
| 68 | |||
| 69 | private Dictionary<string, IWindowsInstallerBackendDecompilerExtension> ExtensionsByTableName { get; } | ||
| 70 | |||
| 71 | private string BaseSourcePath { get; } | ||
| 72 | |||
| 73 | private bool SuppressCustomTables { get; } | ||
| 74 | |||
| 75 | private bool SuppressDroppingEmptyTables { get; } | ||
| 76 | |||
| 77 | private bool SuppressRelativeActionSequencing { get; } | ||
| 78 | |||
| 79 | private bool SuppressUI { get; } | ||
| 80 | |||
| 81 | private bool TreatProductAsModule { get; } | ||
| 82 | |||
| 83 | private OutputType OutputType { get; set; } | ||
| 84 | |||
| 85 | private Dictionary<string, WixActionSymbol> StandardActions { get; } | ||
| 86 | |||
| 87 | private bool Compressed { get; set; } | ||
| 88 | |||
| 89 | private XElement RootElement { get; set; } | ||
| 90 | |||
| 91 | private TableDefinitionCollection TableDefinitions { get; } | ||
| 92 | |||
| 93 | private bool ShortNames { get; set; } | ||
| 94 | |||
| 95 | private string ModularizationGuid { get; set; } | ||
| 96 | |||
| 97 | private XElement UIElement | ||
| 98 | { | ||
| 99 | get | ||
| 100 | { | ||
| 101 | if (null == this.uiElement) | ||
| 102 | { | ||
| 103 | this.uiElement = new XElement(Names.UIElement); | ||
| 104 | this.RootElement.Add(this.uiElement); | ||
| 105 | } | ||
| 106 | |||
| 107 | return this.uiElement; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | private Dictionary<string, XElement> Singletons { get; } = new Dictionary<string, XElement>(); | ||
| 112 | |||
| 113 | private Dictionary<string, XElement> IndexedElements { get; } = new Dictionary<string, XElement>(); | ||
| 114 | |||
| 115 | private Dictionary<string, XElement> PatchTargetFiles { get; } = new Dictionary<string, XElement>(); | ||
| 116 | |||
| 117 | /// <summary> | ||
| 118 | /// Decompile the database file. | ||
| 119 | /// </summary> | ||
| 120 | /// <param name="output">The output to decompile.</param> | ||
| 121 | /// <returns>The serialized WiX source code.</returns> | ||
| 122 | public XDocument Decompile(WindowsInstallerData output) | ||
| 123 | { | ||
| 124 | if (null == output) | ||
| 125 | { | ||
| 126 | throw new ArgumentNullException(nameof(output)); | ||
| 127 | } | ||
| 128 | |||
| 129 | this.OutputType = output.Type; | ||
| 130 | |||
| 131 | // collect the table definitions from the output | ||
| 132 | this.TableDefinitions.Clear(); | ||
| 133 | foreach (var table in output.Tables) | ||
| 134 | { | ||
| 135 | this.TableDefinitions.Add(table.Definition); | ||
| 136 | } | ||
| 137 | |||
| 138 | // add any missing standard and wix-specific table definitions | ||
| 139 | foreach (var tableDefinition in WindowsInstallerTableDefinitions.All) | ||
| 140 | { | ||
| 141 | if (!this.TableDefinitions.Contains(tableDefinition.Name)) | ||
| 142 | { | ||
| 143 | this.TableDefinitions.Add(tableDefinition); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | // add any missing extension table definitions | ||
| 148 | #if TODO_DECOMPILER_EXTENSIONS | ||
| 149 | foreach (var extension in this.Extensions) | ||
| 150 | { | ||
| 151 | this.AddExtension(extension); | ||
| 152 | } | ||
| 153 | #endif | ||
| 154 | |||
| 155 | switch (this.OutputType) | ||
| 156 | { | ||
| 157 | case OutputType.Module: | ||
| 158 | this.RootElement = new XElement(Names.ModuleElement); | ||
| 159 | break; | ||
| 160 | case OutputType.PatchCreation: | ||
| 161 | this.RootElement = new XElement(Names.PatchCreationElement); | ||
| 162 | break; | ||
| 163 | case OutputType.Product: | ||
| 164 | this.RootElement = new XElement(Names.PackageElement); | ||
| 165 | break; | ||
| 166 | default: | ||
| 167 | throw new InvalidOperationException("Unknown output type."); | ||
| 168 | } | ||
| 169 | |||
| 170 | var xWix = new XElement(Names.WixElement, this.RootElement); | ||
| 171 | |||
| 172 | // try to decompile the database file | ||
| 173 | // stop processing if an error previously occurred | ||
| 174 | if (this.Messaging.EncounteredError) | ||
| 175 | { | ||
| 176 | return null; | ||
| 177 | } | ||
| 178 | |||
| 179 | this.InitializeDecompile(output.Tables, output.Codepage); | ||
| 180 | |||
| 181 | // stop processing if an error previously occurred | ||
| 182 | if (this.Messaging.EncounteredError) | ||
| 183 | { | ||
| 184 | return null; | ||
| 185 | } | ||
| 186 | |||
| 187 | // decompile the tables | ||
| 188 | this.DecompileTables(output); | ||
| 189 | |||
| 190 | // finalize the decompiler and its extensions | ||
| 191 | this.FinalizeDecompile(output.Tables); | ||
| 192 | |||
| 193 | // return the XML document only if decompilation completed successfully | ||
| 194 | var document = new XDocument(xWix); | ||
| 195 | return this.Messaging.EncounteredError ? null : document; | ||
| 196 | } | ||
| 197 | |||
| 198 | #if TODO_DECOMPILER_EXTENSIONS | ||
| 199 | private void AddExtension(IWindowsInstallerBackendDecompilerExtension extension) | ||
| 200 | { | ||
| 201 | if (null != extension.TableDefinitions) | ||
| 202 | { | ||
| 203 | foreach (TableDefinition tableDefinition in extension.TableDefinitions) | ||
| 204 | { | ||
| 205 | if (!this.ExtensionsByTableName.ContainsKey(tableDefinition.Name)) | ||
| 206 | { | ||
| 207 | this.ExtensionsByTableName.Add(tableDefinition.Name, extension); | ||
| 208 | } | ||
| 209 | else | ||
| 210 | { | ||
| 211 | this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name)); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
| 215 | } | ||
| 216 | #endif | ||
| 217 | |||
| 218 | internal static Platform? GetPlatformFromTemplateSummaryInformation(string[] template) | ||
| 219 | { | ||
| 220 | if (null != template && 1 < template.Length && null != template[0] && 0 < template[0].Length) | ||
| 221 | { | ||
| 222 | switch (template[0]) | ||
| 223 | { | ||
| 224 | case "Intel": | ||
| 225 | return Platform.X86; | ||
| 226 | case "x64": | ||
| 227 | return Platform.X64; | ||
| 228 | case "Arm64": | ||
| 229 | return Platform.ARM64; | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | return null; | ||
| 234 | } | ||
| 235 | |||
| 236 | /// <summary> | ||
| 237 | /// Gets the element corresponding to the row it came from. | ||
| 238 | /// </summary> | ||
| 239 | /// <param name="row">The row corresponding to the element.</param> | ||
| 240 | /// <returns>The indexed element.</returns> | ||
| 241 | private XElement GetIndexedElement(WixToolset.Data.WindowsInstaller.Row row) => this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); | ||
| 242 | |||
| 243 | /// <summary> | ||
| 244 | /// Gets the element corresponding to the primary key of the given table. | ||
| 245 | /// </summary> | ||
| 246 | /// <param name="table">The table corresponding to the element.</param> | ||
| 247 | /// <param name="primaryKey">The primary key corresponding to the element.</param> | ||
| 248 | /// <returns>The indexed element.</returns> | ||
| 249 | private XElement GetIndexedElement(string table, params string[] primaryKey) => this.IndexedElements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))]; | ||
| 250 | |||
| 251 | /// <summary> | ||
| 252 | /// Tries to get the element corresponding to the primary key of the given table. | ||
| 253 | /// </summary> | ||
| 254 | /// <param name="row">The table corresponding to the element.</param> | ||
| 255 | /// <param name="xElement">The indexed element.</param> | ||
| 256 | /// <returns>Whether the element was found.</returns> | ||
| 257 | private bool TryGetIndexedElement(WixToolset.Data.WindowsInstaller.Row row, out XElement xElement) => this.TryGetIndexedElement(row.TableDefinition.Name, out xElement, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); | ||
| 258 | |||
| 259 | /// <summary> | ||
| 260 | /// Tries to get the element corresponding to the primary key of the given table. | ||
| 261 | /// </summary> | ||
| 262 | /// <param name="table">The table corresponding to the element.</param> | ||
| 263 | /// <param name="xElement">The indexed element.</param> | ||
| 264 | /// <param name="primaryKey">The primary key corresponding to the element.</param> | ||
| 265 | /// <returns>Whether the element was found.</returns> | ||
| 266 | private bool TryGetIndexedElement(string table, out XElement xElement, params string[] primaryKey) => this.IndexedElements.TryGetValue(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), out xElement); | ||
| 267 | |||
| 268 | /// <summary> | ||
| 269 | /// Index an element by its corresponding row. | ||
| 270 | /// </summary> | ||
| 271 | /// <param name="row">The row corresponding to the element.</param> | ||
| 272 | /// <param name="element">The element to index.</param> | ||
| 273 | private void IndexElement(WixToolset.Data.WindowsInstaller.Row row, XElement element) | ||
| 274 | { | ||
| 275 | this.IndexedElements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element); | ||
| 276 | } | ||
| 277 | |||
| 278 | /// <summary> | ||
| 279 | /// Index an element by its corresponding row. | ||
| 280 | /// </summary> | ||
| 281 | /// <param name="element">The element to index.</param> | ||
| 282 | /// <param name="table"></param> | ||
| 283 | /// <param name="primaryKey"></param> | ||
| 284 | private void IndexElement(XElement element, string table, params string[] primaryKey) | ||
| 285 | { | ||
| 286 | this.IndexedElements.Add(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), element); | ||
| 287 | } | ||
| 288 | |||
| 289 | private Dictionary<string, List<XElement>> IndexTableOneToMany(IEnumerable<Row> rows, int column = 0) | ||
| 290 | { | ||
| 291 | return rows | ||
| 292 | .ToLookup(row => row.FieldAsString(column), row => this.GetIndexedElement(row)) | ||
| 293 | .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); | ||
| 294 | } | ||
| 295 | |||
| 296 | private Dictionary<string, List<XElement>> IndexTableOneToMany(TableIndexedCollection tables, string tableName, int column = 0) => this.IndexTableOneToMany(tables[tableName]?.Rows ?? Enumerable.Empty<Row>(), column); | ||
| 297 | |||
| 298 | private Dictionary<string, List<XElement>> IndexTableOneToMany(Table table, int column = 0) => this.IndexTableOneToMany(table?.Rows ?? Enumerable.Empty<Row>(), column); | ||
| 299 | |||
| 300 | private void AddChildToParent(string parentName, XElement xChild, Row row, int column) | ||
| 301 | { | ||
| 302 | var key = row.FieldAsString(column); | ||
| 303 | if (this.TryGetIndexedElement(parentName, out var xParent, key)) | ||
| 304 | { | ||
| 305 | xParent.Add(xChild); | ||
| 306 | } | ||
| 307 | else | ||
| 308 | { | ||
| 309 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row.Fields[column].Column.Name, key, parentName)); | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | private static XAttribute XAttributeIfNotNull(string attributeName, Row row, int column) => row.IsColumnNull(column) ? null : new XAttribute(attributeName, row.FieldAsString(column)); | ||
| 314 | |||
| 315 | private static void SetAttributeIfNotNull(XElement xElement, string attributeName, string value) | ||
| 316 | { | ||
| 317 | if (!String.IsNullOrEmpty(value)) | ||
| 318 | { | ||
| 319 | xElement.SetAttributeValue(attributeName, value); | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | private static void SetAttributeIfNotNull(XElement xElement, string attributeName, int? value) | ||
| 324 | { | ||
| 325 | if (value.HasValue) | ||
| 326 | { | ||
| 327 | xElement.SetAttributeValue(attributeName, value); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | /// <summary> | ||
| 332 | /// Convert an Int32 into a DateTime. | ||
| 333 | /// </summary> | ||
| 334 | /// <param name="value">The Int32 value.</param> | ||
| 335 | /// <returns>The DateTime.</returns> | ||
| 336 | private static DateTime ConvertIntegerToDateTime(int value) | ||
| 337 | { | ||
| 338 | var date = value / 65536; | ||
| 339 | var time = value % 65536; | ||
| 340 | |||
| 341 | return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2); | ||
| 342 | } | ||
| 343 | |||
| 344 | /// <summary> | ||
| 345 | /// Set the common control attributes in a control element. | ||
| 346 | /// </summary> | ||
| 347 | /// <param name="attributes">The control attributes.</param> | ||
| 348 | /// <param name="xControl">The control element.</param> | ||
| 349 | private static void SetControlAttributes(int attributes, XElement xControl) | ||
| 350 | { | ||
| 351 | if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesEnabled)) | ||
| 352 | { | ||
| 353 | xControl.SetAttributeValue("Disabled", "yes"); | ||
| 354 | } | ||
| 355 | |||
| 356 | if (WindowsInstallerConstants.MsidbControlAttributesIndirect == (attributes & WindowsInstallerConstants.MsidbControlAttributesIndirect)) | ||
| 357 | { | ||
| 358 | xControl.SetAttributeValue("Indirect", "yes"); | ||
| 359 | } | ||
| 360 | |||
| 361 | if (WindowsInstallerConstants.MsidbControlAttributesInteger == (attributes & WindowsInstallerConstants.MsidbControlAttributesInteger)) | ||
| 362 | { | ||
| 363 | xControl.SetAttributeValue("Integer", "yes"); | ||
| 364 | } | ||
| 365 | |||
| 366 | if (WindowsInstallerConstants.MsidbControlAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbControlAttributesLeftScroll)) | ||
| 367 | { | ||
| 368 | xControl.SetAttributeValue("LeftScroll", "yes"); | ||
| 369 | } | ||
| 370 | |||
| 371 | if (WindowsInstallerConstants.MsidbControlAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbControlAttributesRightAligned)) | ||
| 372 | { | ||
| 373 | xControl.SetAttributeValue("RightAligned", "yes"); | ||
| 374 | } | ||
| 375 | |||
| 376 | if (WindowsInstallerConstants.MsidbControlAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbControlAttributesRTLRO)) | ||
| 377 | { | ||
| 378 | xControl.SetAttributeValue("RightToLeft", "yes"); | ||
| 379 | } | ||
| 380 | |||
| 381 | if (WindowsInstallerConstants.MsidbControlAttributesSunken == (attributes & WindowsInstallerConstants.MsidbControlAttributesSunken)) | ||
| 382 | { | ||
| 383 | xControl.SetAttributeValue("Sunken", "yes"); | ||
| 384 | } | ||
| 385 | |||
| 386 | if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesVisible)) | ||
| 387 | { | ||
| 388 | xControl.SetAttributeValue("Hidden", "yes"); | ||
| 389 | } | ||
| 390 | } | ||
| 391 | |||
| 392 | /// <summary> | ||
| 393 | /// Creates an action element. | ||
| 394 | /// </summary> | ||
| 395 | /// <param name="actionSymbol">The action from which the element should be created.</param> | ||
| 396 | private void CreateActionElement(WixActionSymbol actionSymbol) | ||
| 397 | { | ||
| 398 | XElement xAction; | ||
| 399 | |||
| 400 | if (this.TryGetIndexedElement("CustomAction", out var _, actionSymbol.Action)) // custom action | ||
| 401 | { | ||
| 402 | xAction = new XElement(Names.CustomElement, | ||
| 403 | new XAttribute("Action", actionSymbol.Action), | ||
| 404 | String.IsNullOrEmpty(actionSymbol.Condition) ? null : new XAttribute("Condition", actionSymbol.Condition)); | ||
| 405 | |||
| 406 | switch (actionSymbol.Sequence) | ||
| 407 | { | ||
| 408 | case (-4): | ||
| 409 | xAction.SetAttributeValue("OnExit", "suspend"); | ||
| 410 | break; | ||
| 411 | case (-3): | ||
| 412 | xAction.SetAttributeValue("OnExit", "error"); | ||
| 413 | break; | ||
| 414 | case (-2): | ||
| 415 | xAction.SetAttributeValue("OnExit", "cancel"); | ||
| 416 | break; | ||
| 417 | case (-1): | ||
| 418 | xAction.SetAttributeValue("OnExit", "success"); | ||
| 419 | break; | ||
| 420 | default: | ||
| 421 | if (null != actionSymbol.Before) | ||
| 422 | { | ||
| 423 | xAction.SetAttributeValue("Before", actionSymbol.Before); | ||
| 424 | } | ||
| 425 | else if (null != actionSymbol.After) | ||
| 426 | { | ||
| 427 | xAction.SetAttributeValue("After", actionSymbol.After); | ||
| 428 | } | ||
| 429 | else if (actionSymbol.Sequence.HasValue) | ||
| 430 | { | ||
| 431 | xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); | ||
| 432 | } | ||
| 433 | break; | ||
| 434 | } | ||
| 435 | } | ||
| 436 | else if (this.TryGetIndexedElement("Dialog", out var _, actionSymbol.Action)) // dialog | ||
| 437 | { | ||
| 438 | xAction = new XElement(Names.CustomElement, | ||
| 439 | new XAttribute("Dialog", actionSymbol.Action), | ||
| 440 | new XAttribute("Condition", actionSymbol.Condition)); | ||
| 441 | |||
| 442 | switch (actionSymbol.Sequence) | ||
| 443 | { | ||
| 444 | case (-4): | ||
| 445 | xAction.SetAttributeValue("OnExit", "suspend"); | ||
| 446 | break; | ||
| 447 | case (-3): | ||
| 448 | xAction.SetAttributeValue("OnExit", "error"); | ||
| 449 | break; | ||
| 450 | case (-2): | ||
| 451 | xAction.SetAttributeValue("OnExit", "cancel"); | ||
| 452 | break; | ||
| 453 | case (-1): | ||
| 454 | xAction.SetAttributeValue("OnExit", "success"); | ||
| 455 | break; | ||
| 456 | default: | ||
| 457 | SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); | ||
| 458 | SetAttributeIfNotNull(xAction, "After", actionSymbol.After); | ||
| 459 | SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); | ||
| 460 | break; | ||
| 461 | } | ||
| 462 | } | ||
| 463 | else // possibly a standard action without suggested sequence information | ||
| 464 | { | ||
| 465 | xAction = this.CreateStandardActionElement(actionSymbol); | ||
| 466 | } | ||
| 467 | |||
| 468 | // add the action element to the appropriate sequence element | ||
| 469 | if (null != xAction) | ||
| 470 | { | ||
| 471 | var sequenceTable = actionSymbol.SequenceTable.ToString(); | ||
| 472 | if (!this.Singletons.TryGetValue(sequenceTable, out var xSequence)) | ||
| 473 | { | ||
| 474 | xSequence = new XElement(Names.WxsNamespace + sequenceTable); | ||
| 475 | |||
| 476 | this.RootElement.Add(xSequence); | ||
| 477 | this.Singletons.Add(sequenceTable, xSequence); | ||
| 478 | } | ||
| 479 | |||
| 480 | try | ||
| 481 | { | ||
| 482 | xSequence.Add(xAction); | ||
| 483 | } | ||
| 484 | catch (ArgumentException) // action/dialog is not valid for this sequence | ||
| 485 | { | ||
| 486 | this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); | ||
| 487 | } | ||
| 488 | } | ||
| 489 | } | ||
| 490 | |||
| 491 | /// <summary> | ||
| 492 | /// Creates a standard action element. | ||
| 493 | /// </summary> | ||
| 494 | /// <param name="actionSymbol">The action row from which the element should be created.</param> | ||
| 495 | /// <returns>The created element.</returns> | ||
| 496 | private XElement CreateStandardActionElement(WixActionSymbol actionSymbol) | ||
| 497 | { | ||
| 498 | XElement xStandardAction = null; | ||
| 499 | |||
| 500 | switch (actionSymbol.Action) | ||
| 501 | { | ||
| 502 | case "AllocateRegistrySpace": | ||
| 503 | case "BindImage": | ||
| 504 | case "CostFinalize": | ||
| 505 | case "CostInitialize": | ||
| 506 | case "CreateFolders": | ||
| 507 | case "CreateShortcuts": | ||
| 508 | case "DeleteServices": | ||
| 509 | case "DuplicateFiles": | ||
| 510 | case "ExecuteAction": | ||
| 511 | case "FileCost": | ||
| 512 | case "InstallAdminPackage": | ||
| 513 | case "InstallFiles": | ||
| 514 | case "InstallFinalize": | ||
| 515 | case "InstallInitialize": | ||
| 516 | case "InstallODBC": | ||
| 517 | case "InstallServices": | ||
| 518 | case "InstallValidate": | ||
| 519 | case "IsolateComponents": | ||
| 520 | case "MigrateFeatureStates": | ||
| 521 | case "MoveFiles": | ||
| 522 | case "MsiPublishAssemblies": | ||
| 523 | case "MsiUnpublishAssemblies": | ||
| 524 | case "PatchFiles": | ||
| 525 | case "ProcessComponents": | ||
| 526 | case "PublishComponents": | ||
| 527 | case "PublishFeatures": | ||
| 528 | case "PublishProduct": | ||
| 529 | case "RegisterClassInfo": | ||
| 530 | case "RegisterComPlus": | ||
| 531 | case "RegisterExtensionInfo": | ||
| 532 | case "RegisterFonts": | ||
| 533 | case "RegisterMIMEInfo": | ||
| 534 | case "RegisterProduct": | ||
| 535 | case "RegisterProgIdInfo": | ||
| 536 | case "RegisterTypeLibraries": | ||
| 537 | case "RegisterUser": | ||
| 538 | case "RemoveDuplicateFiles": | ||
| 539 | case "RemoveEnvironmentStrings": | ||
| 540 | case "RemoveFiles": | ||
| 541 | case "RemoveFolders": | ||
| 542 | case "RemoveIniValues": | ||
| 543 | case "RemoveODBC": | ||
| 544 | case "RemoveRegistryValues": | ||
| 545 | case "RemoveShortcuts": | ||
| 546 | case "SelfRegModules": | ||
| 547 | case "SelfUnregModules": | ||
| 548 | case "SetODBCFolders": | ||
| 549 | case "StartServices": | ||
| 550 | case "StopServices": | ||
| 551 | case "UnpublishComponents": | ||
| 552 | case "UnpublishFeatures": | ||
| 553 | case "UnregisterClassInfo": | ||
| 554 | case "UnregisterComPlus": | ||
| 555 | case "UnregisterExtensionInfo": | ||
| 556 | case "UnregisterFonts": | ||
| 557 | case "UnregisterMIMEInfo": | ||
| 558 | case "UnregisterProgIdInfo": | ||
| 559 | case "UnregisterTypeLibraries": | ||
| 560 | case "ValidateProductID": | ||
| 561 | case "WriteEnvironmentStrings": | ||
| 562 | case "WriteIniValues": | ||
| 563 | case "WriteRegistryValues": | ||
| 564 | xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); | ||
| 565 | break; | ||
| 566 | |||
| 567 | case "AppSearch": | ||
| 568 | this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow); | ||
| 569 | |||
| 570 | if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence)) | ||
| 571 | { | ||
| 572 | xStandardAction = new XElement(Names.AppSearchElement); | ||
| 573 | |||
| 574 | SetAttributeIfNotNull(xStandardAction, "Condition", actionSymbol.Condition); | ||
| 575 | SetAttributeIfNotNull(xStandardAction, "Before", actionSymbol.Before); | ||
| 576 | SetAttributeIfNotNull(xStandardAction, "After", actionSymbol.After); | ||
| 577 | SetAttributeIfNotNull(xStandardAction, "Sequence", actionSymbol.Sequence); | ||
| 578 | |||
| 579 | return xStandardAction; | ||
| 580 | } | ||
| 581 | break; | ||
| 582 | |||
| 583 | case "CCPSearch": | ||
| 584 | case "DisableRollback": | ||
| 585 | case "FindRelatedProducts": | ||
| 586 | case "ForceReboot": | ||
| 587 | case "InstallExecute": | ||
| 588 | case "InstallExecuteAgain": | ||
| 589 | case "LaunchConditions": | ||
| 590 | case "RemoveExistingProducts": | ||
| 591 | case "ResolveSource": | ||
| 592 | case "RMCCPSearch": | ||
| 593 | case "ScheduleReboot": | ||
| 594 | xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); | ||
| 595 | Decompiler.SequenceRelativeAction(actionSymbol, xStandardAction); | ||
| 596 | return xStandardAction; | ||
| 597 | |||
| 598 | default: | ||
| 599 | this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); | ||
| 600 | return null; | ||
| 601 | } | ||
| 602 | |||
| 603 | if (xStandardAction != null) | ||
| 604 | { | ||
| 605 | this.SequenceStandardAction(actionSymbol, xStandardAction); | ||
| 606 | } | ||
| 607 | |||
| 608 | return xStandardAction; | ||
| 609 | } | ||
| 610 | |||
| 611 | /// <summary> | ||
| 612 | /// Applies the condition and sequence to a standard action element based on the action symbol data. | ||
| 613 | /// </summary> | ||
| 614 | /// <param name="actionSymbol">Action data from the database.</param> | ||
| 615 | /// <param name="xAction">Element to be sequenced.</param> | ||
| 616 | private void SequenceStandardAction(WixActionSymbol actionSymbol, XElement xAction) | ||
| 617 | { | ||
| 618 | xAction.SetAttributeValue("Condition", actionSymbol.Condition); | ||
| 619 | |||
| 620 | if ((null != actionSymbol.Before || null != actionSymbol.After) && 0 == actionSymbol.Sequence) | ||
| 621 | { | ||
| 622 | this.Messaging.Write(WarningMessages.DecompiledStandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); | ||
| 623 | } | ||
| 624 | else if (actionSymbol.Sequence.HasValue) | ||
| 625 | { | ||
| 626 | xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); | ||
| 627 | } | ||
| 628 | } | ||
| 629 | |||
| 630 | /// <summary> | ||
| 631 | /// Applies the condition and relative sequence to an action element based on the action row data. | ||
| 632 | /// </summary> | ||
| 633 | /// <param name="actionSymbol">Action data from the database.</param> | ||
| 634 | /// <param name="xAction">Element to be sequenced.</param> | ||
| 635 | private static void SequenceRelativeAction(WixActionSymbol actionSymbol, XElement xAction) | ||
| 636 | { | ||
| 637 | SetAttributeIfNotNull(xAction, "Condition", actionSymbol.Condition); | ||
| 638 | SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); | ||
| 639 | SetAttributeIfNotNull(xAction, "After", actionSymbol.After); | ||
| 640 | SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); | ||
| 641 | } | ||
| 642 | |||
| 643 | /// <summary> | ||
| 644 | /// Ensure that a particular property exists in the decompiled output. | ||
| 645 | /// </summary> | ||
| 646 | /// <param name="id">The identifier of the property.</param> | ||
| 647 | /// <returns>The property element.</returns> | ||
| 648 | private XElement EnsureProperty(string id) | ||
| 649 | { | ||
| 650 | XElement xProperty; | ||
| 651 | |||
| 652 | if (!this.TryGetIndexedElement("Property", out xProperty, id)) | ||
| 653 | { | ||
| 654 | xProperty = new XElement(Names.PropertyElement, new XAttribute("Id", id)); | ||
| 655 | |||
| 656 | this.RootElement.Add(xProperty); | ||
| 657 | this.IndexElement(xProperty, "Property", id); | ||
| 658 | } | ||
| 659 | |||
| 660 | return xProperty; | ||
| 661 | } | ||
| 662 | |||
| 663 | /// <summary> | ||
| 664 | /// Finalize decompilation. | ||
| 665 | /// </summary> | ||
| 666 | /// <param name="tables">The collection of all tables.</param> | ||
| 667 | private void FinalizeDecompile(TableIndexedCollection tables) | ||
| 668 | { | ||
| 669 | if (OutputType.PatchCreation == this.OutputType) | ||
| 670 | { | ||
| 671 | this.FinalizeFamilyFileRangesTable(tables); | ||
| 672 | } | ||
| 673 | else | ||
| 674 | { | ||
| 675 | this.FinalizeSummaryInformationStream(tables); | ||
| 676 | this.FinalizeCheckBoxTable(tables); | ||
| 677 | this.FinalizeComponentTable(tables); | ||
| 678 | this.FinalizeDialogTable(tables); | ||
| 679 | this.FinalizeDuplicateMoveFileTables(tables); | ||
| 680 | this.FinalizeFeatureComponentsTable(tables); | ||
| 681 | this.FinalizeFileTable(tables); | ||
| 682 | this.FinalizeMIMETable(tables); | ||
| 683 | this.FinalizeMsiLockPermissionsExTable(tables); | ||
| 684 | this.FinalizeLockPermissionsTable(tables); | ||
| 685 | this.FinalizeProgIdTable(tables); | ||
| 686 | this.FinalizePropertyTable(tables); | ||
| 687 | this.FinalizeRemoveFileTable(tables); | ||
| 688 | this.FinalizeSearchTables(tables); | ||
| 689 | this.FinalizeShortcutTable(tables); | ||
| 690 | this.FinalizeUpgradeTable(tables); | ||
| 691 | this.FinalizeSequenceTables(tables); | ||
| 692 | this.FinalizeVerbTable(tables); | ||
| 693 | } | ||
| 694 | } | ||
| 695 | |||
| 696 | /// <summary> | ||
| 697 | /// Finalize the CheckBox table. | ||
| 698 | /// </summary> | ||
| 699 | /// <param name="tables">The collection of all tables.</param> | ||
| 700 | /// <remarks> | ||
| 701 | /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with | ||
| 702 | /// a value in the Property column. This is then possibly matched up with a CheckBox row | ||
| 703 | /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table. | ||
| 704 | /// </remarks> | ||
| 705 | private void FinalizeCheckBoxTable(TableIndexedCollection tables) | ||
| 706 | { | ||
| 707 | // if the user has requested to suppress the UI elements, we have nothing to do | ||
| 708 | if (this.SuppressUI) | ||
| 709 | { | ||
| 710 | return; | ||
| 711 | } | ||
| 712 | |||
| 713 | var checkBoxTable = tables["CheckBox"]; | ||
| 714 | var controlTable = tables["Control"]; | ||
| 715 | |||
| 716 | var checkBoxes = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); | ||
| 717 | var checkBoxProperties = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row => false); | ||
| 718 | |||
| 719 | // enumerate through the Control table, adding CheckBox values where appropriate | ||
| 720 | if (null != controlTable) | ||
| 721 | { | ||
| 722 | foreach (var row in controlTable.Rows) | ||
| 723 | { | ||
| 724 | var xControl = this.GetIndexedElement(row); | ||
| 725 | |||
| 726 | if ("CheckBox" == row.FieldAsString(2)) | ||
| 727 | { | ||
| 728 | var property = row.FieldAsString(8); | ||
| 729 | if (!String.IsNullOrEmpty(property) && checkBoxes.TryGetValue(property, out var checkBoxRow)) | ||
| 730 | { | ||
| 731 | // if we've seen this property already, create a reference to it | ||
| 732 | if (checkBoxProperties.TryGetValue(property, out var seen) && seen) | ||
| 733 | { | ||
| 734 | xControl.SetAttributeValue("CheckBoxPropertyRef", property); | ||
| 735 | } | ||
| 736 | else | ||
| 737 | { | ||
| 738 | xControl.SetAttributeValue("Property", property); | ||
| 739 | checkBoxProperties[property] = true; | ||
| 740 | } | ||
| 741 | |||
| 742 | xControl.SetAttributeValue("CheckBoxValue", checkBoxRow.FieldAsString(1)); | ||
| 743 | } | ||
| 744 | else | ||
| 745 | { | ||
| 746 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", row.FieldAsString(8), "CheckBox")); | ||
| 747 | } | ||
| 748 | } | ||
| 749 | } | ||
| 750 | } | ||
| 751 | } | ||
| 752 | |||
| 753 | /// <summary> | ||
| 754 | /// Finalize the Component table. | ||
| 755 | /// </summary> | ||
| 756 | /// <param name="tables">The collection of all tables.</param> | ||
| 757 | /// <remarks> | ||
| 758 | /// Set the keypaths for each component. | ||
| 759 | /// </remarks> | ||
| 760 | private void FinalizeComponentTable(TableIndexedCollection tables) | ||
| 761 | { | ||
| 762 | var componentTable = tables["Component"]; | ||
| 763 | var fileTable = tables["File"]; | ||
| 764 | var odbcDataSourceTable = tables["ODBCDataSource"]; | ||
| 765 | var registryTable = tables["Registry"]; | ||
| 766 | |||
| 767 | // set the component keypaths | ||
| 768 | if (null != componentTable) | ||
| 769 | { | ||
| 770 | foreach (var row in componentTable.Rows) | ||
| 771 | { | ||
| 772 | var attributes = row.FieldAsInteger(3); | ||
| 773 | var keyPath = row.FieldAsString(5); | ||
| 774 | |||
| 775 | if (String.IsNullOrEmpty(keyPath)) | ||
| 776 | { | ||
| 777 | var xComponent = this.GetIndexedElement("Component", row.FieldAsString(0)); | ||
| 778 | xComponent.SetAttributeValue("KeyPath", "yes"); | ||
| 779 | } | ||
| 780 | else if (WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath == (attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) | ||
| 781 | { | ||
| 782 | if (this.TryGetIndexedElement("Registry", out var xRegistry, keyPath)) | ||
| 783 | { | ||
| 784 | if (xRegistry.Name.LocalName == "RegistryValue") | ||
| 785 | { | ||
| 786 | xRegistry.SetAttributeValue("KeyPath", "yes"); | ||
| 787 | } | ||
| 788 | else | ||
| 789 | { | ||
| 790 | this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", keyPath)); | ||
| 791 | } | ||
| 792 | } | ||
| 793 | else | ||
| 794 | { | ||
| 795 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "Registry")); | ||
| 796 | } | ||
| 797 | } | ||
| 798 | else if (WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource == (attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource)) | ||
| 799 | { | ||
| 800 | if (this.TryGetIndexedElement("ODBCDataSource", out var xOdbcDataSource, keyPath)) | ||
| 801 | { | ||
| 802 | xOdbcDataSource.SetAttributeValue("KeyPath", "yes"); | ||
| 803 | } | ||
| 804 | else | ||
| 805 | { | ||
| 806 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "ODBCDataSource")); | ||
| 807 | } | ||
| 808 | } | ||
| 809 | else | ||
| 810 | { | ||
| 811 | if (this.TryGetIndexedElement("File", out var xFile, keyPath)) | ||
| 812 | { | ||
| 813 | xFile.SetAttributeValue("KeyPath", "yes"); | ||
| 814 | } | ||
| 815 | else | ||
| 816 | { | ||
| 817 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "File")); | ||
| 818 | } | ||
| 819 | } | ||
| 820 | } | ||
| 821 | } | ||
| 822 | |||
| 823 | // add the File children elements | ||
| 824 | if (null != fileTable) | ||
| 825 | { | ||
| 826 | foreach (FileRow fileRow in fileTable.Rows) | ||
| 827 | { | ||
| 828 | if (this.TryGetIndexedElement("Component", out var xComponent, fileRow.Component) | ||
| 829 | && this.TryGetIndexedElement(fileRow, out var xFile)) | ||
| 830 | { | ||
| 831 | xComponent.Add(xFile); | ||
| 832 | } | ||
| 833 | else | ||
| 834 | { | ||
| 835 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component")); | ||
| 836 | } | ||
| 837 | } | ||
| 838 | } | ||
| 839 | |||
| 840 | // add the ODBCDataSource children elements | ||
| 841 | if (null != odbcDataSourceTable) | ||
| 842 | { | ||
| 843 | foreach (var row in odbcDataSourceTable.Rows) | ||
| 844 | { | ||
| 845 | if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(1)) | ||
| 846 | && this.TryGetIndexedElement(row, out var xOdbcDataSource)) | ||
| 847 | { | ||
| 848 | xComponent.Add(xOdbcDataSource); | ||
| 849 | } | ||
| 850 | else | ||
| 851 | { | ||
| 852 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); | ||
| 853 | } | ||
| 854 | } | ||
| 855 | } | ||
| 856 | |||
| 857 | // add the Registry children elements | ||
| 858 | if (null != registryTable) | ||
| 859 | { | ||
| 860 | foreach (var row in registryTable.Rows) | ||
| 861 | { | ||
| 862 | if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(5)) | ||
| 863 | && this.TryGetIndexedElement(row, out var xRegistry)) | ||
| 864 | { | ||
| 865 | xComponent.Add(xRegistry); | ||
| 866 | } | ||
| 867 | else | ||
| 868 | { | ||
| 869 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(5), "Component")); | ||
| 870 | } | ||
| 871 | } | ||
| 872 | } | ||
| 873 | } | ||
| 874 | |||
| 875 | /// <summary> | ||
| 876 | /// Finalize the Dialog table. | ||
| 877 | /// </summary> | ||
| 878 | /// <param name="tables">The collection of all tables.</param> | ||
| 879 | /// <remarks> | ||
| 880 | /// Sets the first, default, and cancel control for each dialog and adds all child control | ||
| 881 | /// elements to the dialog. | ||
| 882 | /// </remarks> | ||
| 883 | private void FinalizeDialogTable(TableIndexedCollection tables) | ||
| 884 | { | ||
| 885 | // if the user has requested to suppress the UI elements, we have nothing to do | ||
| 886 | if (this.SuppressUI) | ||
| 887 | { | ||
| 888 | return; | ||
| 889 | } | ||
| 890 | |||
| 891 | var addedControls = new HashSet<XElement>(); | ||
| 892 | |||
| 893 | var controlTable = tables["Control"]; | ||
| 894 | var controlRows = controlTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); | ||
| 895 | |||
| 896 | var dialogTable = tables["Dialog"]; | ||
| 897 | if (null != dialogTable) | ||
| 898 | { | ||
| 899 | foreach (var dialogRow in dialogTable.Rows) | ||
| 900 | { | ||
| 901 | var xDialog = this.GetIndexedElement(dialogRow); | ||
| 902 | var dialogId = dialogRow.FieldAsString(0); | ||
| 903 | |||
| 904 | if (!this.TryGetIndexedElement("Control", out var xControl, dialogId, dialogRow.FieldAsString(7))) | ||
| 905 | { | ||
| 906 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", dialogRow.FieldAsString(7), "Control")); | ||
| 907 | } | ||
| 908 | |||
| 909 | // add tabbable controls | ||
| 910 | while (null != xControl) | ||
| 911 | { | ||
| 912 | var controlId = xControl.Attribute("Id"); | ||
| 913 | var controlRow = controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, controlId)]; | ||
| 914 | |||
| 915 | xControl.SetAttributeValue("TabSkip", "no"); | ||
| 916 | |||
| 917 | xDialog.Add(xControl); | ||
| 918 | addedControls.Add(xControl); | ||
| 919 | |||
| 920 | var controlNext = controlRow.FieldAsString(10); | ||
| 921 | if (!String.IsNullOrEmpty(controlNext)) | ||
| 922 | { | ||
| 923 | if (this.TryGetIndexedElement("Control", out xControl, dialogId, controlNext)) | ||
| 924 | { | ||
| 925 | // looped back to the first control in the dialog | ||
| 926 | if (addedControls.Contains(xControl)) | ||
| 927 | { | ||
| 928 | xControl = null; | ||
| 929 | } | ||
| 930 | } | ||
| 931 | else | ||
| 932 | { | ||
| 933 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", controlNext, "Control")); | ||
| 934 | } | ||
| 935 | } | ||
| 936 | else | ||
| 937 | { | ||
| 938 | xControl = null; | ||
| 939 | } | ||
| 940 | } | ||
| 941 | |||
| 942 | // set default control | ||
| 943 | var controlDefault = dialogRow.FieldAsString(8); | ||
| 944 | if (!String.IsNullOrEmpty(controlDefault)) | ||
| 945 | { | ||
| 946 | if (this.TryGetIndexedElement("Control", out var xDefaultControl, dialogId, controlDefault)) | ||
| 947 | { | ||
| 948 | xDefaultControl.SetAttributeValue("Default", "yes"); | ||
| 949 | } | ||
| 950 | else | ||
| 951 | { | ||
| 952 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(dialogRow[8]), "Control")); | ||
| 953 | } | ||
| 954 | } | ||
| 955 | |||
| 956 | // set cancel control | ||
| 957 | var controlCancel = dialogRow.FieldAsString(8); | ||
| 958 | if (!String.IsNullOrEmpty(controlCancel)) | ||
| 959 | { | ||
| 960 | if (this.TryGetIndexedElement("Control", out var xCancelControl, dialogId, controlCancel)) | ||
| 961 | { | ||
| 962 | xCancelControl.SetAttributeValue("Cancel", "yes"); | ||
| 963 | } | ||
| 964 | else | ||
| 965 | { | ||
| 966 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(dialogRow[9]), "Control")); | ||
| 967 | } | ||
| 968 | } | ||
| 969 | } | ||
| 970 | } | ||
| 971 | |||
| 972 | // add the non-tabbable controls to the dialog | ||
| 973 | if (null != controlTable) | ||
| 974 | { | ||
| 975 | foreach (var controlRow in controlTable.Rows) | ||
| 976 | { | ||
| 977 | var dialogId = controlRow.FieldAsString(0); | ||
| 978 | if (!this.TryGetIndexedElement("Dialog", out var xDialog, dialogId)) | ||
| 979 | { | ||
| 980 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Dialog")); | ||
| 981 | continue; | ||
| 982 | } | ||
| 983 | |||
| 984 | var xControl = this.GetIndexedElement(controlRow); | ||
| 985 | if (!addedControls.Contains(xControl)) | ||
| 986 | { | ||
| 987 | xControl.SetAttributeValue("TabSkip", "yes"); | ||
| 988 | xDialog.Add(xControl); | ||
| 989 | } | ||
| 990 | } | ||
| 991 | } | ||
| 992 | } | ||
| 993 | |||
| 994 | /// <summary> | ||
| 995 | /// Finalize the DuplicateFile and MoveFile tables. | ||
| 996 | /// </summary> | ||
| 997 | /// <param name="tables">The collection of all tables.</param> | ||
| 998 | /// <remarks> | ||
| 999 | /// Sets the source/destination property/directory for each DuplicateFile or | ||
| 1000 | /// MoveFile row. | ||
| 1001 | /// </remarks> | ||
| 1002 | private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables) | ||
| 1003 | { | ||
| 1004 | var duplicateFileTable = tables["DuplicateFile"]; | ||
| 1005 | if (null != duplicateFileTable) | ||
| 1006 | { | ||
| 1007 | foreach (var row in duplicateFileTable.Rows) | ||
| 1008 | { | ||
| 1009 | var xCopyFile = this.GetIndexedElement(row); | ||
| 1010 | var destination = row.FieldAsString(4); | ||
| 1011 | if (!String.IsNullOrEmpty(destination)) | ||
| 1012 | { | ||
| 1013 | if (this.TryGetIndexedElement("Directory", out var _, destination)) | ||
| 1014 | { | ||
| 1015 | xCopyFile.SetAttributeValue("DestinationDirectory", destination); | ||
| 1016 | } | ||
| 1017 | else | ||
| 1018 | { | ||
| 1019 | xCopyFile.SetAttributeValue("DestinationProperty", destination); | ||
| 1020 | } | ||
| 1021 | } | ||
| 1022 | } | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | var moveFileTable = tables["MoveFile"]; | ||
| 1026 | if (null != moveFileTable) | ||
| 1027 | { | ||
| 1028 | foreach (var row in moveFileTable.Rows) | ||
| 1029 | { | ||
| 1030 | var xCopyFile = this.GetIndexedElement(row); | ||
| 1031 | var source = row.FieldAsString(4); | ||
| 1032 | if (!String.IsNullOrEmpty(source)) | ||
| 1033 | { | ||
| 1034 | if (this.TryGetIndexedElement("Directory", out var _, source)) | ||
| 1035 | { | ||
| 1036 | xCopyFile.SetAttributeValue("SourceDirectory", source); | ||
| 1037 | } | ||
| 1038 | else | ||
| 1039 | { | ||
| 1040 | xCopyFile.SetAttributeValue("SourceProperty", source); | ||
| 1041 | } | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | var destination = row.FieldAsString(5); | ||
| 1045 | if (this.TryGetIndexedElement("Directory", out var _, destination)) | ||
| 1046 | { | ||
| 1047 | xCopyFile.SetAttributeValue("DestinationDirectory", destination); | ||
| 1048 | } | ||
| 1049 | else | ||
| 1050 | { | ||
| 1051 | xCopyFile.SetAttributeValue("DestinationProperty", destination); | ||
| 1052 | } | ||
| 1053 | } | ||
| 1054 | } | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | /// <summary> | ||
| 1058 | /// Finalize the FamilyFileRanges table. | ||
| 1059 | /// </summary> | ||
| 1060 | /// <param name="tables">The collection of all tables.</param> | ||
| 1061 | private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables) | ||
| 1062 | { | ||
| 1063 | var familyFileRangesTable = tables["FamilyFileRanges"]; | ||
| 1064 | if (null != familyFileRangesTable) | ||
| 1065 | { | ||
| 1066 | foreach (var row in familyFileRangesTable.Rows) | ||
| 1067 | { | ||
| 1068 | var xProtectRange = new XElement(Names.ProtectRangeElement); | ||
| 1069 | |||
| 1070 | if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) | ||
| 1071 | { | ||
| 1072 | var retainOffsets = row.FieldAsString(2).Split(','); | ||
| 1073 | var retainLengths = row.FieldAsString(3).Split(','); | ||
| 1074 | |||
| 1075 | if (retainOffsets.Length == retainLengths.Length) | ||
| 1076 | { | ||
| 1077 | for (var i = 0; i < retainOffsets.Length; i++) | ||
| 1078 | { | ||
| 1079 | if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal)) | ||
| 1080 | { | ||
| 1081 | xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i].Substring(2), 16)); | ||
| 1082 | } | ||
| 1083 | else | ||
| 1084 | { | ||
| 1085 | xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture)); | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal)) | ||
| 1089 | { | ||
| 1090 | xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i].Substring(2), 16)); | ||
| 1091 | } | ||
| 1092 | else | ||
| 1093 | { | ||
| 1094 | xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture)); | ||
| 1095 | } | ||
| 1096 | } | ||
| 1097 | } | ||
| 1098 | else | ||
| 1099 | { | ||
| 1100 | // TODO: warn | ||
| 1101 | } | ||
| 1102 | } | ||
| 1103 | else if (!row.IsColumnNull(2) || !row.IsColumnNull(3)) | ||
| 1104 | { | ||
| 1105 | // TODO: warn about mismatch between columns | ||
| 1106 | } | ||
| 1107 | |||
| 1108 | this.IndexElement(row, xProtectRange); | ||
| 1109 | } | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | var usedProtectRanges = new HashSet<XElement>(); | ||
| 1113 | var externalFilesTable = tables["ExternalFiles"]; | ||
| 1114 | if (null != externalFilesTable) | ||
| 1115 | { | ||
| 1116 | foreach (var row in externalFilesTable.Rows) | ||
| 1117 | { | ||
| 1118 | if (this.TryGetIndexedElement(row, out var xExternalFile) | ||
| 1119 | && this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, row.FieldAsString(0), row.FieldAsString(0))) | ||
| 1120 | { | ||
| 1121 | xExternalFile.Add(xProtectRange); | ||
| 1122 | usedProtectRanges.Add(xProtectRange); | ||
| 1123 | } | ||
| 1124 | } | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"]; | ||
| 1128 | if (null != targetFiles_OptionalDataTable) | ||
| 1129 | { | ||
| 1130 | var targetImagesTable = tables["TargetImages"]; | ||
| 1131 | var targetImageRows = targetImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); | ||
| 1132 | |||
| 1133 | var upgradedImagesTable = tables["UpgradedImages"]; | ||
| 1134 | var upgradedImagesRows = upgradedImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); | ||
| 1135 | |||
| 1136 | foreach (var row in targetFiles_OptionalDataTable.Rows) | ||
| 1137 | { | ||
| 1138 | var xTargetFile = this.PatchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)]; | ||
| 1139 | |||
| 1140 | if (!targetImageRows.TryGetValue(row.FieldAsString(0), out var targetImageRow)) | ||
| 1141 | { | ||
| 1142 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); | ||
| 1143 | continue; | ||
| 1144 | } | ||
| 1145 | |||
| 1146 | if (!upgradedImagesRows.TryGetValue(row.FieldAsString(3), out var upgradedImagesRow)) | ||
| 1147 | { | ||
| 1148 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", row.FieldAsString(3), "UpgradedImages")); | ||
| 1149 | continue; | ||
| 1150 | } | ||
| 1151 | |||
| 1152 | if (this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, upgradedImagesRow.FieldAsString(4), row.FieldAsString(1))) | ||
| 1153 | { | ||
| 1154 | xTargetFile.Add(xProtectRange); | ||
| 1155 | usedProtectRanges.Add(xProtectRange); | ||
| 1156 | } | ||
| 1157 | } | ||
| 1158 | } | ||
| 1159 | |||
| 1160 | if (null != familyFileRangesTable) | ||
| 1161 | { | ||
| 1162 | foreach (var row in familyFileRangesTable.Rows) | ||
| 1163 | { | ||
| 1164 | var xProtectRange = this.GetIndexedElement(row); | ||
| 1165 | |||
| 1166 | if (!usedProtectRanges.Contains(xProtectRange)) | ||
| 1167 | { | ||
| 1168 | var xProtectFile = new XElement(Names.ProtectFileElement, new XAttribute("File", row.FieldAsString(1))); | ||
| 1169 | xProtectFile.Add(xProtectRange); | ||
| 1170 | |||
| 1171 | this.AddChildToParent("ImageFamilies", xProtectFile, row, 0); | ||
| 1172 | } | ||
| 1173 | } | ||
| 1174 | } | ||
| 1175 | } | ||
| 1176 | |||
| 1177 | /// <summary> | ||
| 1178 | /// Finalize the FeatureComponents table. | ||
| 1179 | /// </summary> | ||
| 1180 | /// <param name="tables">The collection of all tables.</param> | ||
| 1181 | /// <remarks> | ||
| 1182 | /// Since tables specifying references to the FeatureComponents table have references to | ||
| 1183 | /// the Feature and Component table separately, but not the FeatureComponents table specifically, | ||
| 1184 | /// the FeatureComponents table and primary features must be decompiled during finalization. | ||
| 1185 | /// </remarks> | ||
| 1186 | private void FinalizeFeatureComponentsTable(TableIndexedCollection tables) | ||
| 1187 | { | ||
| 1188 | var classTable = tables["Class"]; | ||
| 1189 | if (null != classTable) | ||
| 1190 | { | ||
| 1191 | foreach (var row in classTable.Rows) | ||
| 1192 | { | ||
| 1193 | this.SetPrimaryFeature(row, 11, 2); | ||
| 1194 | } | ||
| 1195 | } | ||
| 1196 | |||
| 1197 | var extensionTable = tables["Extension"]; | ||
| 1198 | if (null != extensionTable) | ||
| 1199 | { | ||
| 1200 | foreach (var row in extensionTable.Rows) | ||
| 1201 | { | ||
| 1202 | this.SetPrimaryFeature(row, 4, 1); | ||
| 1203 | } | ||
| 1204 | } | ||
| 1205 | |||
| 1206 | var msiAssemblyTable = tables["MsiAssembly"]; | ||
| 1207 | if (null != msiAssemblyTable) | ||
| 1208 | { | ||
| 1209 | foreach (var row in msiAssemblyTable.Rows) | ||
| 1210 | { | ||
| 1211 | this.SetPrimaryFeature(row, 1, 0); | ||
| 1212 | } | ||
| 1213 | } | ||
| 1214 | |||
| 1215 | var publishComponentTable = tables["PublishComponent"]; | ||
| 1216 | if (null != publishComponentTable) | ||
| 1217 | { | ||
| 1218 | foreach (var row in publishComponentTable.Rows) | ||
| 1219 | { | ||
| 1220 | this.SetPrimaryFeature(row, 4, 2); | ||
| 1221 | } | ||
| 1222 | } | ||
| 1223 | |||
| 1224 | var typeLibTable = tables["TypeLib"]; | ||
| 1225 | if (null != typeLibTable) | ||
| 1226 | { | ||
| 1227 | foreach (var row in typeLibTable.Rows) | ||
| 1228 | { | ||
| 1229 | this.SetPrimaryFeature(row, 6, 2); | ||
| 1230 | } | ||
| 1231 | } | ||
| 1232 | } | ||
| 1233 | |||
| 1234 | /// <summary> | ||
| 1235 | /// Finalize the File table. | ||
| 1236 | /// </summary> | ||
| 1237 | /// <param name="tables">The collection of all tables.</param> | ||
| 1238 | /// <remarks> | ||
| 1239 | /// Sets the source, diskId, and assembly information for each file. | ||
| 1240 | /// </remarks> | ||
| 1241 | private void FinalizeFileTable(TableIndexedCollection tables) | ||
| 1242 | { | ||
| 1243 | // index the media table by media id | ||
| 1244 | var mediaTable = tables["Media"]; | ||
| 1245 | var mediaRows = new RowDictionary<MediaRow>(mediaTable); | ||
| 1246 | |||
| 1247 | // set the disk identifiers and sources for files | ||
| 1248 | foreach (var fileRow in tables["File"]?.Rows.Cast<FileRow>() ?? Enumerable.Empty<FileRow>()) | ||
| 1249 | { | ||
| 1250 | var xFile = this.GetIndexedElement("File", fileRow.File); | ||
| 1251 | |||
| 1252 | // Don't bother processing files that are orphaned (and won't show up in the output anyway) | ||
| 1253 | if (null != xFile.Parent) | ||
| 1254 | { | ||
| 1255 | // set the diskid | ||
| 1256 | if (null != mediaTable) | ||
| 1257 | { | ||
| 1258 | foreach (MediaRow mediaRow in mediaTable.Rows) | ||
| 1259 | { | ||
| 1260 | if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1) | ||
| 1261 | { | ||
| 1262 | xFile.SetAttributeValue("DiskId", mediaRow.DiskId); | ||
| 1263 | break; | ||
| 1264 | } | ||
| 1265 | } | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | var fileId = xFile?.Attribute("Id")?.Value; | ||
| 1269 | var fileCompressed = xFile?.Attribute("Compressed")?.Value; | ||
| 1270 | var fileShortName = xFile?.Attribute("ShortName")?.Value; | ||
| 1271 | var fileName = xFile?.Attribute("Name")?.Value; | ||
| 1272 | |||
| 1273 | // set the source (done here because it requires information from the Directory table) | ||
| 1274 | if (OutputType.Module == this.OutputType) | ||
| 1275 | { | ||
| 1276 | xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId, '.', this.ModularizationGuid.Substring(1, 36).Replace('-', '_'))); | ||
| 1277 | } | ||
| 1278 | else if (fileCompressed == "yes" || (fileCompressed != "no" && this.Compressed) || (OutputType.Product == this.OutputType && this.TreatProductAsModule)) | ||
| 1279 | { | ||
| 1280 | xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId)); | ||
| 1281 | } | ||
| 1282 | else // uncompressed | ||
| 1283 | { | ||
| 1284 | var name = (!this.ShortNames && !String.IsNullOrEmpty(fileName)) ? fileName : fileShortName ?? fileName; | ||
| 1285 | |||
| 1286 | if (this.Compressed) // uncompressed at the root of the source image | ||
| 1287 | { | ||
| 1288 | xFile.SetAttributeValue("Source", String.Concat("SourceDir", Path.DirectorySeparatorChar, name)); | ||
| 1289 | } | ||
| 1290 | else | ||
| 1291 | { | ||
| 1292 | var sourcePath = this.GetSourcePath(xFile); | ||
| 1293 | xFile.SetAttributeValue("Source", Path.Combine(sourcePath, name)); | ||
| 1294 | } | ||
| 1295 | } | ||
| 1296 | } | ||
| 1297 | } | ||
| 1298 | |||
| 1299 | // set the file assemblies and manifests | ||
| 1300 | foreach (var row in tables["MsiAssembly"]?.Rows ?? Enumerable.Empty<Row>()) | ||
| 1301 | { | ||
| 1302 | if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) | ||
| 1303 | { | ||
| 1304 | foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) | ||
| 1305 | { | ||
| 1306 | xFile.SetAttributeValue("AssemblyManifest", row.FieldAsString(2)); | ||
| 1307 | xFile.SetAttributeValue("AssemblyApplication", row.FieldAsString(3)); | ||
| 1308 | xFile.SetAttributeValue("Assembly", row.FieldAsInteger(4) == 0 ? ".net" : "win32"); | ||
| 1309 | } | ||
| 1310 | } | ||
| 1311 | else | ||
| 1312 | { | ||
| 1313 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); | ||
| 1314 | } | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | // nest the TypeLib elements | ||
| 1318 | foreach (var row in tables["TypeLib"]?.Rows ?? Enumerable.Empty<Row>()) | ||
| 1319 | { | ||
| 1320 | var xComponent = this.GetIndexedElement("Component", row.FieldAsString(2)); | ||
| 1321 | var xTypeLib = this.GetIndexedElement(row); | ||
| 1322 | |||
| 1323 | foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) | ||
| 1324 | { | ||
| 1325 | xFile.Add(xTypeLib); | ||
| 1326 | } | ||
| 1327 | } | ||
| 1328 | } | ||
| 1329 | |||
| 1330 | /// <summary> | ||
| 1331 | /// Finalize the MIME table. | ||
| 1332 | /// </summary> | ||
| 1333 | /// <param name="tables">The collection of all tables.</param> | ||
| 1334 | /// <remarks> | ||
| 1335 | /// There is a foreign key shared between the MIME and Extension | ||
| 1336 | /// tables so either one would be valid to be decompiled first, so | ||
| 1337 | /// the only safe way to nest the MIME elements is to do it during finalize. | ||
| 1338 | /// </remarks> | ||
| 1339 | private void FinalizeMIMETable(TableIndexedCollection tables) | ||
| 1340 | { | ||
| 1341 | var extensionRows = tables["Extension"]?.Rows ?? Enumerable.Empty<Row>(); | ||
| 1342 | foreach (var row in extensionRows) | ||
| 1343 | { | ||
| 1344 | // set the default MIME element for this extension | ||
| 1345 | var mimeRef = row.FieldAsString(3); | ||
| 1346 | if (null != mimeRef) | ||
| 1347 | { | ||
| 1348 | if (this.TryGetIndexedElement("MIME", out var xMime, mimeRef)) | ||
| 1349 | { | ||
| 1350 | xMime.SetAttributeValue("Default", "yes"); | ||
| 1351 | } | ||
| 1352 | else | ||
| 1353 | { | ||
| 1354 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); | ||
| 1355 | } | ||
| 1356 | } | ||
| 1357 | } | ||
| 1358 | |||
| 1359 | var extensionsByExtensionId = this.IndexTableOneToMany(extensionRows); | ||
| 1360 | |||
| 1361 | foreach (var row in tables["MIME"]?.Rows ?? Enumerable.Empty<Row>()) | ||
| 1362 | { | ||
| 1363 | var xMime = this.GetIndexedElement(row); | ||
| 1364 | |||
| 1365 | if (extensionsByExtensionId.TryGetValue(row.FieldAsString(1), out var xExtensions)) | ||
| 1366 | { | ||
| 1367 | foreach (var extension in xExtensions) | ||
| 1368 | { | ||
| 1369 | extension.Add(xMime); | ||
| 1370 | } | ||
| 1371 | } | ||
| 1372 | else | ||
| 1373 | { | ||
| 1374 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(1), "Extension")); | ||
| 1375 | } | ||
| 1376 | } | ||
| 1377 | } | ||
| 1378 | |||
| 1379 | /// <summary> | ||
| 1380 | /// Finalize the ProgId table. | ||
| 1381 | /// </summary> | ||
| 1382 | /// <param name="tables">The collection of all tables.</param> | ||
| 1383 | /// <remarks> | ||
| 1384 | /// Enumerates through all the Class rows, looking for child ProgIds (these are the | ||
| 1385 | /// default ProgIds for a given Class). Then go through the ProgId table and add any | ||
| 1386 | /// remaining ProgIds for each Class. This happens during finalize because there is | ||
| 1387 | /// a circular dependency between the Class and ProgId tables. | ||
| 1388 | /// </remarks> | ||
| 1389 | private void FinalizeProgIdTable(TableIndexedCollection tables) | ||
| 1390 | { | ||
| 1391 | // add the default ProgIds for each class (and index the class table) | ||
| 1392 | var classRows = tables["Class"]?.Rows?.Where(row => row.FieldAsString(3) != null) ?? Enumerable.Empty<Row>(); | ||
| 1393 | |||
| 1394 | var classesByCLSID = this.IndexTableOneToMany(classRows); | ||
| 1395 | |||
| 1396 | var addedProgIds = new Dictionary<XElement, string>(); | ||
| 1397 | |||
| 1398 | foreach (var row in classRows) | ||
| 1399 | { | ||
| 1400 | var clsid = row.FieldAsString(0); | ||
| 1401 | var xClass = this.GetIndexedElement(row); | ||
| 1402 | |||
| 1403 | if (this.TryGetIndexedElement("ProgId", out var xProgId, row.FieldAsString(3))) | ||
| 1404 | { | ||
| 1405 | if (addedProgIds.TryGetValue(xProgId, out var progid)) | ||
| 1406 | { | ||
| 1407 | this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, row.FieldAsString(0), row.FieldAsString(3), progid)); | ||
| 1408 | } | ||
| 1409 | else | ||
| 1410 | { | ||
| 1411 | xClass.Add(xProgId); | ||
| 1412 | addedProgIds.Add(xProgId, clsid); | ||
| 1413 | } | ||
| 1414 | } | ||
| 1415 | else | ||
| 1416 | { | ||
| 1417 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", row.FieldAsString(3), "ProgId")); | ||
| 1418 | } | ||
| 1419 | } | ||
| 1420 | |||
| 1421 | // add the remaining non-default ProgId entries for each class | ||
| 1422 | foreach (var row in tables["ProgId"]?.Rows ?? Enumerable.Empty<Row>()) | ||
| 1423 | { | ||
| 1424 | var clsid = row.FieldAsString(2); | ||
| 1425 | var xProgId = this.GetIndexedElement(row); | ||
| 1426 | |||
| 1427 | if (!addedProgIds.ContainsKey(xProgId) && null != clsid && null == xProgId.Parent) | ||
| 1428 | { | ||
| 1429 | if (classesByCLSID.TryGetValue(clsid, out var xClasses)) | ||
| 1430 | { | ||
| 1431 | foreach (var xClass in xClasses) | ||
| 1432 | { | ||
| 1433 | xClass.Add(xProgId); | ||
| 1434 | addedProgIds.Add(xProgId, clsid); | ||
| 1435 | } | ||
| 1436 | } | ||
| 1437 | else | ||
| 1438 | { | ||
| 1439 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", row.FieldAsString(2), "Class")); | ||
| 1440 | } | ||
| 1441 | } | ||
| 1442 | } | ||
| 1443 | |||
| 1444 | // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension | ||
| 1445 | var componentsById = this.IndexTableOneToMany(tables, "Component"); | ||
| 1446 | |||
| 1447 | foreach (var row in tables["Extension"]?.Rows?.Where(row => row.FieldAsString(2) != null) ?? Enumerable.Empty<Row>()) | ||
| 1448 | { | ||
| 1449 | var xProgId = this.GetIndexedElement("ProgId", row.FieldAsString(2)); | ||
| 1450 | |||
| 1451 | // Haven't added the progId yet and it doesn't have a parent progId | ||
| 1452 | if (!addedProgIds.ContainsKey(xProgId) && null == xProgId.Parent) | ||
| 1453 | { | ||
| 1454 | if (componentsById.TryGetValue(row.FieldAsString(1), out var xComponents)) | ||
| 1455 | { | ||
| 1456 | foreach (var xComponent in xComponents) | ||
| 1457 | { | ||
| 1458 | xComponent.Add(xProgId); | ||
| 1459 | } | ||
| 1460 | } | ||
| 1461 | else | ||
| 1462 | { | ||
| 1463 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); | ||
| 1464 | } | ||
| 1465 | } | ||
| 1466 | } | ||
| 1467 | } | ||
| 1468 | |||
| 1469 | /// <summary> | ||
| 1470 | /// Finalize the Property table. | ||
| 1471 | /// </summary> | ||
| 1472 | /// <param name="tables">The collection of all tables.</param> | ||
| 1473 | /// <remarks> | ||
| 1474 | /// Removes properties that are generated from other entries. | ||
| 1475 | /// </remarks> | ||
| 1476 | private void FinalizePropertyTable(TableIndexedCollection tables) | ||
| 1477 | { | ||
| 1478 | foreach (var row in tables["CustomAction"]?.Rows ?? Enumerable.Empty<Row>()) | ||
| 1479 | { | ||
| 1480 | // If no other fields on the property are set we must have created it in the backend. | ||
| 1481 | var bits = row.FieldAsInteger(1); | ||
| 1482 | if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget) | ||
| 1483 | && WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript) | ||
| 1484 | && this.TryGetIndexedElement("Property", out var xProperty, row.FieldAsString(0)) | ||
| 1485 | && String.IsNullOrEmpty(xProperty.Attribute("Value")?.Value) | ||
| 1486 | && xProperty.Attribute("Secure")?.Value != "yes" | ||
| 1487 | && xProperty.Attribute("SuppressModularization")?.Value != "yes") | ||
| 1488 | { | ||
| 1489 | xProperty.Remove(); | ||
| 1490 | } | ||
| 1491 | } | ||
| 1492 | } | ||
| 1493 | |||
| 1494 | /// <summary> | ||
| 1495 | /// Finalize the RemoveFile table. | ||
| 1496 | /// </summary> | ||
| 1497 | /// <param name="tables">The collection of all tables.</param> | ||
| 1498 | /// <remarks> | ||
| 1499 | /// Sets the directory/property for each RemoveFile row. | ||
| 1500 | /// </remarks> | ||
| 1501 | private void FinalizeRemoveFileTable(TableIndexedCollection tables) | ||
| 1502 | { | ||
| 1503 | foreach (var row in tables["RemoveFile"]?.Rows ?? Enumerable.Empty<Row>()) | ||
| 1504 | { | ||
| 1505 | var xRemove = this.GetIndexedElement(row); | ||
| 1506 | var property = row.FieldAsString(3); | ||
| 1507 | |||
| 1508 | if (this.TryGetIndexedElement("Directory", out var _, property)) | ||
| 1509 | { | ||
| 1510 | xRemove.SetAttributeValue("Directory", property); | ||
| 1511 | } | ||
| 1512 | else | ||
| 1513 | { | ||
| 1514 | xRemove.SetAttributeValue("Property", property); | ||
| 1515 | } | ||
| 1516 | } | ||
| 1517 | } | ||
| 1518 | |||
| 1519 | /// <summary> | ||
| 1520 | /// Finalize the LockPermissions or MsiLockPermissionsEx table. | ||
| 1521 | /// </summary> | ||
| 1522 | /// <param name="tables">The collection of all tables.</param> | ||
| 1523 | /// <param name="tableName">Which table to finalize.</param> | ||
| 1524 | /// <remarks> | ||
| 1525 | /// Nests the Permission elements below their parent elements. There are no declared foreign | ||
| 1526 | /// keys for the parents of the LockPermissions table. | ||
| 1527 | /// </remarks> | ||
| 1528 | private void FinalizePermissionsTable(TableIndexedCollection tables, string tableName) | ||
| 1529 | { | ||
| 1530 | var createFoldersById = this.IndexTableOneToMany(tables, tableName); | ||
| 1531 | |||
| 1532 | foreach (var row in tables[tableName]?.Rows ?? Enumerable.Empty<Row>()) | ||
| 1533 | { | ||
| 1534 | var id = row.FieldAsString(0); | ||
| 1535 | var table = row.FieldAsString(1); | ||
| 1536 | var xPermission = this.GetIndexedElement(row); | ||
| 1537 | |||
| 1538 | if ("CreateFolder" == table) | ||
| 1539 | { | ||
| 1540 | if (createFoldersById.TryGetValue(id, out var xCreateFolders)) | ||
| 1541 | { | ||
| 1542 | foreach (var xCreateFolder in xCreateFolders) | ||
| 1543 | { | ||
| 1544 | xCreateFolder.Add(xPermission); | ||
| 1545 | } | ||
| 1546 | } | ||
| 1547 | else | ||
| 1548 | { | ||
| 1549 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); | ||
| 1550 | } | ||
| 1551 | } | ||
| 1552 | else | ||
| 1553 | { | ||
| 1554 | if (this.TryGetIndexedElement(table, out var xParent, id)) | ||
| 1555 | { | ||
| 1556 | xParent.Add(xPermission); | ||
| 1557 | } | ||
| 1558 | else | ||
| 1559 | { | ||
| 1560 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); | ||
| 1561 | } | ||
| 1562 | } | ||
| 1563 | } | ||
| 1564 | } | ||
| 1565 | |||
| 1566 | /// <summary> | ||
| 1567 | /// Finalize the LockPermissions table. | ||
| 1568 | /// </summary> | ||
| 1569 | /// <param name="tables">The collection of all tables.</param> | ||
| 1570 | /// <remarks> | ||
| 1571 | /// Nests the Permission elements below their parent elements. There are no declared foreign | ||
| 1572 | /// keys for the parents of the LockPermissions table. | ||
| 1573 | /// </remarks> | ||
| 1574 | private void FinalizeLockPermissionsTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "LockPermissions"); | ||
| 1575 | |||
| 1576 | /// <summary> | ||
| 1577 | /// Finalize the MsiLockPermissionsEx table. | ||
| 1578 | /// </summary> | ||
| 1579 | /// <param name="tables">The collection of all tables.</param> | ||
| 1580 | /// <remarks> | ||
| 1581 | /// Nests the PermissionEx elements below their parent elements. There are no declared foreign | ||
| 1582 | /// keys for the parents of the MsiLockPermissionsEx table. | ||
| 1583 | /// </remarks> | ||
| 1584 | private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "MsiLockPermissionsEx"); | ||
| 1585 | |||
| 1586 | private static Dictionary<string, List<string>> IndexTable(Table table, int keyColumn, int? dataColumn) | ||
| 1587 | { | ||
| 1588 | if (table == null) | ||
| 1589 | { | ||
| 1590 | return new Dictionary<string, List<string>>(); | ||
| 1591 | } | ||
| 1592 | |||
| 1593 | return table.Rows | ||
| 1594 | .ToLookup(row => row.FieldAsString(keyColumn), row => dataColumn.HasValue ? row.FieldAsString(dataColumn.Value) : null) | ||
| 1595 | .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); | ||
| 1596 | } | ||
| 1597 | |||
| 1598 | private static XElement FindComplianceDrive(XElement xSearch) | ||
| 1599 | { | ||
| 1600 | var xComplianceDrive = xSearch.Element(Names.ComplianceDriveElement); | ||
| 1601 | if (null == xComplianceDrive) | ||
| 1602 | { | ||
| 1603 | xComplianceDrive = new XElement(Names.ComplianceDriveElement); | ||
| 1604 | xSearch.Add(xComplianceDrive); | ||
| 1605 | } | ||
| 1606 | |||
| 1607 | return xComplianceDrive; | ||
| 1608 | } | ||
| 1609 | |||
| 1610 | /// <summary> | ||
| 1611 | /// Finalize the search tables. | ||
| 1612 | /// </summary> | ||
| 1613 | /// <param name="tables">The collection of all tables.</param> | ||
| 1614 | /// <remarks>Does all the complex linking required for the search tables.</remarks> | ||
| 1615 | private void FinalizeSearchTables(TableIndexedCollection tables) | ||
| 1616 | { | ||
| 1617 | var appSearches = IndexTable(tables["AppSearch"], keyColumn: 1, dataColumn: 0); | ||
| 1618 | var ccpSearches = IndexTable(tables["CCPSearch"], keyColumn: 0, dataColumn: null); | ||
| 1619 | var drLocators = tables["DrLocator"]?.Rows.ToDictionary(row => this.GetIndexedElement(row), row => row); | ||
| 1620 | |||
| 1621 | var xComplianceCheck = new XElement(Names.ComplianceCheckElement); | ||
| 1622 | if (ccpSearches.Keys.Any(ccpSignature => !appSearches.ContainsKey(ccpSignature))) | ||
| 1623 | { | ||
| 1624 | this.RootElement.Add(xComplianceCheck); | ||
| 1625 | } | ||
| 1626 | |||
| 1627 | // index the locator tables by their signatures | ||
| 1628 | var locators = | ||
| 1629 | new[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" } | ||
| 1630 | .SelectMany(table => tables[table]?.Rows ?? Enumerable.Empty<Row>()) | ||
| 1631 | .ToLookup(row => row.FieldAsString(0), row => row) | ||
| 1632 | .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); | ||
| 1633 | |||
| 1634 | // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef) | ||
| 1635 | foreach (var locatorRows in locators.Values) | ||
| 1636 | { | ||
| 1637 | var firstDrLocator = -1; | ||
| 1638 | |||
| 1639 | for (var i = 0; i < locatorRows.Count; i++) | ||
| 1640 | { | ||
| 1641 | var locatorRow = (Row)locatorRows[i]; | ||
| 1642 | |||
| 1643 | if ("DrLocator" == locatorRow.TableDefinition.Name) | ||
| 1644 | { | ||
| 1645 | if (-1 == firstDrLocator) | ||
| 1646 | { | ||
| 1647 | firstDrLocator = i; | ||
| 1648 | } | ||
| 1649 | |||
| 1650 | if ("CCP_DRIVE" == Convert.ToString(locatorRow[1])) | ||
| 1651 | { | ||
| 1652 | locatorRows.RemoveAt(i); | ||
| 1653 | locatorRows.Insert(firstDrLocator, locatorRow); | ||
| 1654 | break; | ||
| 1655 | } | ||
| 1656 | } | ||
| 1657 | } | ||
| 1658 | } | ||
| 1659 | |||
| 1660 | var xUsedSearches = new HashSet<XElement>(); | ||
| 1661 | var xUnusedSearches = new Dictionary<string, XElement>(); | ||
| 1662 | |||
| 1663 | foreach (var signature in locators.Keys) | ||
| 1664 | { | ||
| 1665 | var locatorRows = locators[signature]; | ||
| 1666 | var xSignatureSearches = new List<XElement>(); | ||
| 1667 | |||
| 1668 | foreach (var locatorRow in locatorRows) | ||
| 1669 | { | ||
| 1670 | var used = true; | ||
| 1671 | var xSearch = this.GetIndexedElement(locatorRow); | ||
| 1672 | |||
| 1673 | if ("Signature" == locatorRow.TableDefinition.Name && 0 < xSignatureSearches.Count) | ||
| 1674 | { | ||
| 1675 | foreach (var xSearchParent in xSignatureSearches) | ||
| 1676 | { | ||
| 1677 | if (!xUsedSearches.Contains(xSearch)) | ||
| 1678 | { | ||
| 1679 | xSearchParent.Add(xSearch); | ||
| 1680 | xUsedSearches.Add(xSearch); | ||
| 1681 | } | ||
| 1682 | else | ||
| 1683 | { | ||
| 1684 | var xFileSearchRef = new XElement(Names.FileSearchRefElement, | ||
| 1685 | new XAttribute("Id", signature)); | ||
| 1686 | |||
| 1687 | xSearchParent.Add(xFileSearchRef); | ||
| 1688 | } | ||
| 1689 | } | ||
| 1690 | } | ||
| 1691 | else if ("DrLocator" == locatorRow.TableDefinition.Name && !locatorRow.IsColumnNull(1)) | ||
| 1692 | { | ||
| 1693 | var parentSignature = locatorRow.FieldAsString(1); | ||
| 1694 | |||
| 1695 | if ("CCP_DRIVE" == parentSignature) | ||
| 1696 | { | ||
| 1697 | if (appSearches.ContainsKey(signature) | ||
| 1698 | && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) | ||
| 1699 | { | ||
| 1700 | foreach (var propertyId in appSearchPropertyIds) | ||
| 1701 | { | ||
| 1702 | var xProperty = this.EnsureProperty(propertyId); | ||
| 1703 | |||
| 1704 | if (ccpSearches.ContainsKey(signature)) | ||
| 1705 | { | ||
| 1706 | xProperty.SetAttributeValue("ComplianceCheck", "yes"); | ||
| 1707 | } | ||
| 1708 | |||
| 1709 | var xComplianceDrive = FindComplianceDrive(xProperty); | ||
| 1710 | |||
| 1711 | if (!xUsedSearches.Contains(xSearch)) | ||
| 1712 | { | ||
| 1713 | xComplianceDrive.Add(xSearch); | ||
| 1714 | xUsedSearches.Add(xSearch); | ||
| 1715 | } | ||
| 1716 | else | ||
| 1717 | { | ||
| 1718 | var directorySearchRef = new XElement(Names.DirectorySearchRefElement, | ||
| 1719 | new XAttribute("Id", signature), | ||
| 1720 | XAttributeIfNotNull("Parent", locatorRow, 1), | ||
| 1721 | XAttributeIfNotNull("Path", locatorRow, 2)); | ||
| 1722 | |||
| 1723 | xComplianceDrive.Add(directorySearchRef); | ||
| 1724 | xSignatureSearches.Add(directorySearchRef); | ||
| 1725 | } | ||
| 1726 | } | ||
| 1727 | } | ||
| 1728 | else if (ccpSearches.ContainsKey(signature)) | ||
| 1729 | { | ||
| 1730 | var xComplianceDrive = FindComplianceDrive(xComplianceCheck); | ||
| 1731 | |||
| 1732 | if (!xUsedSearches.Contains(xSearch)) | ||
| 1733 | { | ||
| 1734 | xComplianceDrive.Add(xSearch); | ||
| 1735 | xUsedSearches.Add(xSearch); | ||
| 1736 | } | ||
| 1737 | else | ||
| 1738 | { | ||
| 1739 | var directorySearchRef = new XElement(Names.DirectorySearchRefElement, | ||
| 1740 | new XAttribute("Id", signature), | ||
| 1741 | XAttributeIfNotNull("Parent", locatorRow, 1), | ||
| 1742 | XAttributeIfNotNull("Path", locatorRow, 2)); | ||
| 1743 | |||
| 1744 | xComplianceDrive.Add(directorySearchRef); | ||
| 1745 | xSignatureSearches.Add(directorySearchRef); | ||
| 1746 | } | ||
| 1747 | } | ||
| 1748 | } | ||
| 1749 | else | ||
| 1750 | { | ||
| 1751 | var usedDrLocator = false; | ||
| 1752 | |||
| 1753 | if (locators.TryGetValue(parentSignature, out var parentLocatorRows)) | ||
| 1754 | { | ||
| 1755 | foreach (var parentLocatorRow in parentLocatorRows) | ||
| 1756 | { | ||
| 1757 | if ("DrLocator" == parentLocatorRow.TableDefinition.Name) | ||
| 1758 | { | ||
| 1759 | var xParentSearch = this.GetIndexedElement(parentLocatorRow); | ||
| 1760 | |||
| 1761 | if (xParentSearch.HasElements) | ||
| 1762 | { | ||
| 1763 | var parentDrLocatorRow = drLocators[xParentSearch]; | ||
| 1764 | var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, | ||
| 1765 | new XAttribute("Id", parentSignature), | ||
| 1766 | XAttributeIfNotNull("Parent", parentDrLocatorRow, 1), | ||
| 1767 | XAttributeIfNotNull("Path", parentDrLocatorRow, 2)); | ||
| 1768 | |||
| 1769 | xParentSearch = xDirectorySearchRef; | ||
| 1770 | xUnusedSearches.Add(parentSignature, xDirectorySearchRef); | ||
| 1771 | } | ||
| 1772 | |||
| 1773 | if (!xUsedSearches.Contains(xSearch)) | ||
| 1774 | { | ||
| 1775 | xParentSearch.Add(xSearch); | ||
| 1776 | xUsedSearches.Add(xSearch); | ||
| 1777 | usedDrLocator = true; | ||
| 1778 | } | ||
| 1779 | else | ||
| 1780 | { | ||
| 1781 | var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, | ||
| 1782 | new XAttribute("Id", signature), | ||
| 1783 | new XAttribute("Parent", parentSignature), | ||
| 1784 | XAttributeIfNotNull("Path", locatorRow, 2)); | ||
| 1785 | |||
| 1786 | xParentSearch.Add(xSearch); | ||
| 1787 | usedDrLocator = true; | ||
| 1788 | } | ||
| 1789 | } | ||
| 1790 | else if ("RegLocator" == parentLocatorRow.TableDefinition.Name) | ||
| 1791 | { | ||
| 1792 | var xParentSearch = this.GetIndexedElement(parentLocatorRow); | ||
| 1793 | |||
| 1794 | xParentSearch.Add(xSearch); | ||
| 1795 | xUsedSearches.Add(xSearch); | ||
| 1796 | usedDrLocator = true; | ||
| 1797 | } | ||
| 1798 | } | ||
| 1799 | |||
| 1800 | // keep track of unused DrLocator rows | ||
| 1801 | if (!usedDrLocator) | ||
| 1802 | { | ||
| 1803 | xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); | ||
| 1804 | } | ||
| 1805 | } | ||
| 1806 | else | ||
| 1807 | { | ||
| 1808 | // TODO: warn | ||
| 1809 | } | ||
| 1810 | } | ||
| 1811 | } | ||
| 1812 | else if (appSearches.ContainsKey(signature) | ||
| 1813 | && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) | ||
| 1814 | { | ||
| 1815 | foreach (var propertyId in appSearchPropertyIds) | ||
| 1816 | { | ||
| 1817 | var xProperty = this.EnsureProperty(propertyId); | ||
| 1818 | |||
| 1819 | if (ccpSearches.ContainsKey(signature)) | ||
| 1820 | { | ||
| 1821 | xProperty.SetAttributeValue("ComplianceCheck", "yes"); | ||
| 1822 | } | ||
| 1823 | |||
| 1824 | if (!xUsedSearches.Contains(xSearch)) | ||
| 1825 | { | ||
| 1826 | xProperty.Add(xSearch); | ||
| 1827 | xUsedSearches.Add(xSearch); | ||
| 1828 | } | ||
| 1829 | else if ("RegLocator" == locatorRow.TableDefinition.Name) | ||
| 1830 | { | ||
| 1831 | var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, | ||
| 1832 | new XAttribute("Id", signature)); | ||
| 1833 | |||
| 1834 | xProperty.Add(xRegistrySearchRef); | ||
| 1835 | xSignatureSearches.Add(xRegistrySearchRef); | ||
| 1836 | } | ||
| 1837 | else | ||
| 1838 | { | ||
| 1839 | // TODO: warn about unavailable Ref element | ||
| 1840 | } | ||
| 1841 | } | ||
| 1842 | } | ||
| 1843 | else if (ccpSearches.ContainsKey(signature)) | ||
| 1844 | { | ||
| 1845 | if (!xUsedSearches.Contains(xSearch)) | ||
| 1846 | { | ||
| 1847 | xComplianceCheck.Add(xSearch); | ||
| 1848 | xUsedSearches.Add(xSearch); | ||
| 1849 | } | ||
| 1850 | else if ("RegLocator" == locatorRow.TableDefinition.Name) | ||
| 1851 | { | ||
| 1852 | var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, | ||
| 1853 | new XAttribute("Id", signature)); | ||
| 1854 | |||
| 1855 | xComplianceCheck.Add(xRegistrySearchRef); | ||
| 1856 | xSignatureSearches.Add(xRegistrySearchRef); | ||
| 1857 | } | ||
| 1858 | else | ||
| 1859 | { | ||
| 1860 | // TODO: warn about unavailable Ref element | ||
| 1861 | } | ||
| 1862 | } | ||
| 1863 | else | ||
| 1864 | { | ||
| 1865 | if (xSearch.Name.LocalName == "DirectorySearch" || xSearch.Name.LocalName == "RegistrySearch") | ||
| 1866 | { | ||
| 1867 | xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); | ||
| 1868 | } | ||
| 1869 | else | ||
| 1870 | { | ||
| 1871 | // TODO: warn | ||
| 1872 | used = false; | ||
| 1873 | } | ||
| 1874 | } | ||
| 1875 | |||
| 1876 | // keep track of the search elements for this signature so that nested searches go in the proper parents | ||
| 1877 | if (used) | ||
| 1878 | { | ||
| 1879 | xSignatureSearches.Add(xSearch); | ||
| 1880 | } | ||
| 1881 | } | ||
| 1882 | } | ||
| 1883 | |||
| 1884 | // Iterate through the unused elements through a sorted list of their ids so the output is deterministic. | ||
| 1885 | foreach (var unusedSearch in xUnusedSearches.OrderBy(kvp => kvp.Key)) | ||
| 1886 | { | ||
| 1887 | var used = false; | ||
| 1888 | |||
| 1889 | XElement xLeafDirectorySearch = null; | ||
| 1890 | var xUnusedSearch = unusedSearch.Value; | ||
| 1891 | var xParent = xUnusedSearch; | ||
| 1892 | var updatedLeaf = true; | ||
| 1893 | while (updatedLeaf) | ||
| 1894 | { | ||
| 1895 | updatedLeaf = false; | ||
| 1896 | |||
| 1897 | var xDirectorySearch = xParent.Element(Names.DirectorySearchElement); | ||
| 1898 | if (xDirectorySearch != null) | ||
| 1899 | { | ||
| 1900 | xParent = xLeafDirectorySearch = xDirectorySearch; | ||
| 1901 | updatedLeaf = true; | ||
| 1902 | } | ||
| 1903 | } | ||
| 1904 | |||
| 1905 | if (xLeafDirectorySearch != null) | ||
| 1906 | { | ||
| 1907 | var leafDirectorySearchId = xLeafDirectorySearch.Attribute("Id").Value; | ||
| 1908 | if (appSearches.TryGetValue(leafDirectorySearchId, out var appSearchPropertyIds)) | ||
| 1909 | { | ||
| 1910 | var xProperty = this.EnsureProperty(appSearchPropertyIds[0]); | ||
| 1911 | xProperty.Add(xUnusedSearch); | ||
| 1912 | used = true; | ||
| 1913 | } | ||
| 1914 | else if (ccpSearches.ContainsKey(leafDirectorySearchId)) | ||
| 1915 | { | ||
| 1916 | xComplianceCheck.Add(xUnusedSearch); | ||
| 1917 | used = true; | ||
| 1918 | } | ||
| 1919 | else | ||
| 1920 | { | ||
| 1921 | // TODO: warn | ||
| 1922 | } | ||
| 1923 | } | ||
| 1924 | |||
| 1925 | if (!used) | ||
| 1926 | { | ||
| 1927 | // TODO: warn | ||
| 1928 | } | ||
| 1929 | } | ||
| 1930 | } | ||
| 1931 | |||
| 1932 | /// <summary> | ||
| 1933 | /// Finalize the Shortcut table. | ||
| 1934 | /// </summary> | ||
| 1935 | /// <param name="tables">The collection of all tables.</param> | ||
| 1936 | /// <remarks> | ||
| 1937 | /// Sets Advertise to yes if Target points to a Feature. | ||
| 1938 | /// Occurs during finalization because it has to check against every feature row. | ||
| 1939 | /// </remarks> | ||
| 1940 | private void FinalizeShortcutTable(TableIndexedCollection tables) | ||
| 1941 | { | ||
| 1942 | var shortcutTable = tables["Shortcut"]; | ||
| 1943 | if (null == shortcutTable) | ||
| 1944 | { | ||
| 1945 | return; | ||
| 1946 | } | ||
| 1947 | |||
| 1948 | foreach (var row in shortcutTable.Rows) | ||
| 1949 | { | ||
| 1950 | var xShortcut = this.GetIndexedElement(row); | ||
| 1951 | |||
| 1952 | var target = row.FieldAsString(4); | ||
| 1953 | |||
| 1954 | if (this.TryGetIndexedElement("Feature", out var _, target)) | ||
| 1955 | { | ||
| 1956 | xShortcut.SetAttributeValue("Advertise", "yes"); | ||
| 1957 | this.SetPrimaryFeature(row, 4, 3); | ||
| 1958 | } | ||
| 1959 | else | ||
| 1960 | { | ||
| 1961 | // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element | ||
| 1962 | xShortcut.SetAttributeValue("Target", target); | ||
| 1963 | } | ||
| 1964 | } | ||
| 1965 | } | ||
| 1966 | |||
| 1967 | /// <summary> | ||
| 1968 | /// Finalize the sequence tables. | ||
| 1969 | /// </summary> | ||
| 1970 | /// <param name="tables">The collection of all tables.</param> | ||
| 1971 | /// <remarks> | ||
| 1972 | /// Creates the sequence elements. Occurs during finalization because its | ||
| 1973 | /// not known if sequences refer to custom actions or dialogs during decompilation. | ||
| 1974 | /// </remarks> | ||
| 1975 | private void FinalizeSequenceTables(TableIndexedCollection tables) | ||
| 1976 | { | ||
| 1977 | // finalize the normal sequence tables | ||
| 1978 | if (OutputType.Product == this.OutputType && !this.TreatProductAsModule) | ||
| 1979 | { | ||
| 1980 | foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) | ||
| 1981 | { | ||
| 1982 | var sequenceTableName = sequenceTable.WindowsInstallerTableName(); | ||
| 1983 | |||
| 1984 | // if suppressing UI elements, skip UI-related sequence tables | ||
| 1985 | if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) | ||
| 1986 | { | ||
| 1987 | continue; | ||
| 1988 | } | ||
| 1989 | |||
| 1990 | var table = tables[sequenceTableName]; | ||
| 1991 | |||
| 1992 | if (null != table) | ||
| 1993 | { | ||
| 1994 | var actionSymbols = new List<WixActionSymbol>(); | ||
| 1995 | var needAbsoluteScheduling = this.SuppressRelativeActionSequencing; | ||
| 1996 | var nonSequencedActionRows = new Dictionary<string, WixActionSymbol>(); | ||
| 1997 | var suppressedRelativeActionRows = new Dictionary<string, WixActionSymbol>(); | ||
| 1998 | |||
| 1999 | // create a sorted array of actions in this table | ||
| 2000 | foreach (var row in table.Rows) | ||
| 2001 | { | ||
| 2002 | var action = row.FieldAsString(0); | ||
| 2003 | var actionSymbol = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, action)); | ||
| 2004 | |||
| 2005 | actionSymbol.Action = action; | ||
| 2006 | |||
| 2007 | if (!row.IsColumnNull(1)) | ||
| 2008 | { | ||
| 2009 | actionSymbol.Condition = row.FieldAsString(1); | ||
| 2010 | } | ||
| 2011 | |||
| 2012 | actionSymbol.Sequence = row.FieldAsInteger(2); | ||
| 2013 | |||
| 2014 | actionSymbol.SequenceTable = sequenceTable; | ||
| 2015 | |||
| 2016 | actionSymbols.Add(actionSymbol); | ||
| 2017 | } | ||
| 2018 | actionSymbols = actionSymbols.OrderBy(t => t.Sequence).ToList(); | ||
| 2019 | |||
| 2020 | for (var i = 0; i < actionSymbols.Count && !needAbsoluteScheduling; i++) | ||
| 2021 | { | ||
| 2022 | var actionSymbol = actionSymbols[i]; | ||
| 2023 | this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var standardActionRow); | ||
| 2024 | |||
| 2025 | // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions | ||
| 2026 | if ("AppSearch" == actionSymbol.Action || null == standardActionRow || actionSymbol.Condition != standardActionRow.Condition) | ||
| 2027 | { | ||
| 2028 | WixActionSymbol previousActionSymbol = null; | ||
| 2029 | WixActionSymbol nextActionSymbol = null; | ||
| 2030 | |||
| 2031 | // find the previous action row if there is one | ||
| 2032 | if (0 <= i - 1) | ||
| 2033 | { | ||
| 2034 | previousActionSymbol = actionSymbols[i - 1]; | ||
| 2035 | } | ||
| 2036 | |||
| 2037 | // find the next action row if there is one | ||
| 2038 | if (actionSymbols.Count > i + 1) | ||
| 2039 | { | ||
| 2040 | nextActionSymbol = actionSymbols[i + 1]; | ||
| 2041 | } | ||
| 2042 | |||
| 2043 | // the logic for setting the before or after attribute for an action: | ||
| 2044 | // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced. | ||
| 2045 | // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it. | ||
| 2046 | // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it. | ||
| 2047 | // 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it. | ||
| 2048 | // 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it. | ||
| 2049 | // 6. If this action is AppSearch and has all standard information, ignore it. | ||
| 2050 | // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information. | ||
| 2051 | // 8. Everything must be absolutely sequenced. | ||
| 2052 | if ((null != previousActionSymbol && actionSymbol.Sequence == previousActionSymbol.Sequence) || (null != nextActionSymbol && actionSymbol.Sequence == nextActionSymbol.Sequence)) | ||
| 2053 | { | ||
| 2054 | needAbsoluteScheduling = true; | ||
| 2055 | } | ||
| 2056 | else if (null != nextActionSymbol && this.StandardActions.ContainsKey(nextActionSymbol.Id.Id) && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) | ||
| 2057 | { | ||
| 2058 | actionSymbol.Before = nextActionSymbol.Action; | ||
| 2059 | } | ||
| 2060 | else if (null != previousActionSymbol && this.StandardActions.ContainsKey(previousActionSymbol.Id.Id) && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence) | ||
| 2061 | { | ||
| 2062 | actionSymbol.After = previousActionSymbol.Action; | ||
| 2063 | } | ||
| 2064 | else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence && previousActionSymbol.Before != actionSymbol.Action) | ||
| 2065 | { | ||
| 2066 | actionSymbol.After = previousActionSymbol.Action; | ||
| 2067 | } | ||
| 2068 | else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence != previousActionSymbol.Sequence && null != nextActionSymbol && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) | ||
| 2069 | { | ||
| 2070 | actionSymbol.Before = nextActionSymbol.Action; | ||
| 2071 | } | ||
| 2072 | else if ("AppSearch" == actionSymbol.Action && null != standardActionRow && actionSymbol.Sequence == standardActionRow.Sequence && actionSymbol.Condition == standardActionRow.Condition) | ||
| 2073 | { | ||
| 2074 | // ignore an AppSearch row which has the WiX standard sequence and a standard condition | ||
| 2075 | } | ||
| 2076 | else if (null != standardActionRow && actionSymbol.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers | ||
| 2077 | { | ||
| 2078 | nonSequencedActionRows.Add(actionSymbol.Id.Id, actionSymbol); | ||
| 2079 | } | ||
| 2080 | else if (0 < actionSymbol.Sequence) | ||
| 2081 | { | ||
| 2082 | needAbsoluteScheduling = true; | ||
| 2083 | } | ||
| 2084 | } | ||
| 2085 | else | ||
| 2086 | { | ||
| 2087 | suppressedRelativeActionRows.Add(actionSymbol.Id.Id, actionSymbol); | ||
| 2088 | } | ||
| 2089 | } | ||
| 2090 | |||
| 2091 | // create the actions now that we know if they must be absolutely or relatively scheduled | ||
| 2092 | foreach (var actionRow in actionSymbols) | ||
| 2093 | { | ||
| 2094 | var key = actionRow.Id.Id; | ||
| 2095 | |||
| 2096 | if (needAbsoluteScheduling) | ||
| 2097 | { | ||
| 2098 | // remove any before/after information to ensure this is absolutely sequenced | ||
| 2099 | actionRow.Before = null; | ||
| 2100 | actionRow.After = null; | ||
| 2101 | } | ||
| 2102 | else if (nonSequencedActionRows.ContainsKey(key)) | ||
| 2103 | { | ||
| 2104 | // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after) | ||
| 2105 | actionRow.Sequence = 0; | ||
| 2106 | } | ||
| 2107 | else if (suppressedRelativeActionRows.ContainsKey(key)) | ||
| 2108 | { | ||
| 2109 | // skip the suppressed relatively scheduled action rows | ||
| 2110 | continue; | ||
| 2111 | } | ||
| 2112 | |||
| 2113 | // create the action element | ||
| 2114 | this.CreateActionElement(actionRow); | ||
| 2115 | } | ||
| 2116 | } | ||
| 2117 | } | ||
| 2118 | } | ||
| 2119 | else if (OutputType.Module == this.OutputType || this.TreatProductAsModule) // finalize the Module sequence tables | ||
| 2120 | { | ||
| 2121 | foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) | ||
| 2122 | { | ||
| 2123 | var sequenceTableName = sequenceTable.WindowsInstallerTableName(); | ||
| 2124 | |||
| 2125 | // if suppressing UI elements, skip UI-related sequence tables | ||
| 2126 | if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) | ||
| 2127 | { | ||
| 2128 | continue; | ||
| 2129 | } | ||
| 2130 | |||
| 2131 | var table = tables[String.Concat("Module", sequenceTableName)]; | ||
| 2132 | |||
| 2133 | if (null != table) | ||
| 2134 | { | ||
| 2135 | foreach (var row in table.Rows) | ||
| 2136 | { | ||
| 2137 | var actionRow = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, row.FieldAsString(0))); | ||
| 2138 | |||
| 2139 | actionRow.Action = row.FieldAsString(0); | ||
| 2140 | |||
| 2141 | if (!row.IsColumnNull(1)) | ||
| 2142 | { | ||
| 2143 | actionRow.Sequence = row.FieldAsInteger(1); | ||
| 2144 | } | ||
| 2145 | |||
| 2146 | if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) | ||
| 2147 | { | ||
| 2148 | switch (row.FieldAsInteger(3)) | ||
| 2149 | { | ||
| 2150 | case 0: | ||
| 2151 | actionRow.Before = row.FieldAsString(2); | ||
| 2152 | break; | ||
| 2153 | case 1: | ||
| 2154 | actionRow.After = row.FieldAsString(2); | ||
| 2155 | break; | ||
| 2156 | default: | ||
| 2157 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); | ||
| 2158 | break; | ||
| 2159 | } | ||
| 2160 | } | ||
| 2161 | |||
| 2162 | if (!row.IsColumnNull(4)) | ||
| 2163 | { | ||
| 2164 | actionRow.Condition = row.FieldAsString(4); | ||
| 2165 | } | ||
| 2166 | |||
| 2167 | actionRow.SequenceTable = sequenceTable; | ||
| 2168 | |||
| 2169 | // create action elements for non-standard actions | ||
| 2170 | if (!this.StandardActions.ContainsKey(actionRow.Id.Id) || null != actionRow.After || null != actionRow.Before) | ||
| 2171 | { | ||
| 2172 | this.CreateActionElement(actionRow); | ||
| 2173 | } | ||
| 2174 | } | ||
| 2175 | } | ||
| 2176 | } | ||
| 2177 | } | ||
| 2178 | } | ||
| 2179 | |||
| 2180 | /// <summary> | ||
| 2181 | /// Finalize the Upgrade table. | ||
| 2182 | /// </summary> | ||
| 2183 | /// <param name="tables">The collection of all tables.</param> | ||
| 2184 | /// <remarks> | ||
| 2185 | /// Decompile the rows from the Upgrade and LaunchCondition tables | ||
| 2186 | /// created by the MajorUpgrade element. | ||
| 2187 | /// </remarks> | ||
| 2188 | private void FinalizeUpgradeTable(TableIndexedCollection tables) | ||
| 2189 | { | ||
| 2190 | var launchConditionTable = tables["LaunchCondition"]; | ||
| 2191 | var upgradeTable = tables["Upgrade"]; | ||
| 2192 | string downgradeErrorMessage = null; | ||
| 2193 | string disallowUpgradeErrorMessage = null; | ||
| 2194 | |||
| 2195 | // find the DowngradePreventedCondition launch condition message | ||
| 2196 | if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count) | ||
| 2197 | { | ||
| 2198 | foreach (var launchRow in launchConditionTable.Rows) | ||
| 2199 | { | ||
| 2200 | if (WixUpgradeConstants.DowngradePreventedCondition == Convert.ToString(launchRow[0])) | ||
| 2201 | { | ||
| 2202 | downgradeErrorMessage = Convert.ToString(launchRow[1]); | ||
| 2203 | } | ||
| 2204 | else if (WixUpgradeConstants.UpgradePreventedCondition == Convert.ToString(launchRow[0])) | ||
| 2205 | { | ||
| 2206 | disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]); | ||
| 2207 | } | ||
| 2208 | } | ||
| 2209 | } | ||
| 2210 | |||
| 2211 | if (null != upgradeTable && 0 < upgradeTable.Rows.Count) | ||
| 2212 | { | ||
| 2213 | XElement xMajorUpgrade = null; | ||
| 2214 | |||
| 2215 | foreach (UpgradeRow upgradeRow in upgradeTable.Rows) | ||
| 2216 | { | ||
| 2217 | if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty) | ||
| 2218 | { | ||
| 2219 | var attr = upgradeRow.Attributes; | ||
| 2220 | var removeFeatures = upgradeRow.Remove; | ||
| 2221 | xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); | ||
| 2222 | |||
| 2223 | if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) | ||
| 2224 | { | ||
| 2225 | xMajorUpgrade.SetAttributeValue("AllowSameVersionUpgrades", "yes"); | ||
| 2226 | } | ||
| 2227 | |||
| 2228 | if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures != (attr & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) | ||
| 2229 | { | ||
| 2230 | xMajorUpgrade.SetAttributeValue("MigrateFeatures", "no"); | ||
| 2231 | } | ||
| 2232 | |||
| 2233 | if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) | ||
| 2234 | { | ||
| 2235 | xMajorUpgrade.SetAttributeValue("IgnoreRemoveFailure", "yes"); | ||
| 2236 | } | ||
| 2237 | |||
| 2238 | if (!String.IsNullOrEmpty(removeFeatures)) | ||
| 2239 | { | ||
| 2240 | xMajorUpgrade.SetAttributeValue("RemoveFeatures", removeFeatures); | ||
| 2241 | } | ||
| 2242 | } | ||
| 2243 | else if (WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) | ||
| 2244 | { | ||
| 2245 | xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); | ||
| 2246 | xMajorUpgrade.SetAttributeValue("DowngradeErrorMessage", downgradeErrorMessage); | ||
| 2247 | } | ||
| 2248 | } | ||
| 2249 | |||
| 2250 | if (xMajorUpgrade != null) | ||
| 2251 | { | ||
| 2252 | if (String.IsNullOrEmpty(downgradeErrorMessage)) | ||
| 2253 | { | ||
| 2254 | xMajorUpgrade.SetAttributeValue("AllowDowngrades", "yes"); | ||
| 2255 | } | ||
| 2256 | |||
| 2257 | if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage)) | ||
| 2258 | { | ||
| 2259 | xMajorUpgrade.SetAttributeValue("Disallow", "yes"); | ||
| 2260 | xMajorUpgrade.SetAttributeValue("DisallowUpgradeErrorMessage", disallowUpgradeErrorMessage); | ||
| 2261 | } | ||
| 2262 | |||
| 2263 | var scheduledType = DetermineMajorUpgradeScheduling(tables); | ||
| 2264 | if (scheduledType != "afterInstallValidate") | ||
| 2265 | { | ||
| 2266 | xMajorUpgrade.SetAttributeValue("Schedule", scheduledType); | ||
| 2267 | } | ||
| 2268 | |||
| 2269 | this.RootElement.Add(xMajorUpgrade); | ||
| 2270 | } | ||
| 2271 | } | ||
| 2272 | } | ||
| 2273 | |||
| 2274 | /// <summary> | ||
| 2275 | /// Finalize the Verb table. | ||
| 2276 | /// </summary> | ||
| 2277 | /// <param name="tables">The collection of all tables.</param> | ||
| 2278 | /// <remarks> | ||
| 2279 | /// The Extension table is a foreign table for the Verb table, but the | ||
| 2280 | /// foreign key is only part of the primary key of the Extension table, | ||
| 2281 | /// so it needs special logic to be nested properly. | ||
| 2282 | /// </remarks> | ||
| 2283 | private void FinalizeVerbTable(TableIndexedCollection tables) | ||
| 2284 | { | ||
| 2285 | var xExtensions = this.IndexTableOneToMany(tables["Extension"]); | ||
| 2286 | |||
| 2287 | var verbTable = tables["Verb"]; | ||
| 2288 | if (null != verbTable) | ||
| 2289 | { | ||
| 2290 | foreach (var row in verbTable.Rows) | ||
| 2291 | { | ||
| 2292 | if (xExtensions.TryGetValue(row.FieldAsString(0), out var xVerbExtensions)) | ||
| 2293 | { | ||
| 2294 | var xVerb = this.GetIndexedElement(row); | ||
| 2295 | |||
| 2296 | foreach (var xVerbExtension in xVerbExtensions) | ||
| 2297 | { | ||
| 2298 | xVerbExtension.Add(xVerb); | ||
| 2299 | } | ||
| 2300 | } | ||
| 2301 | else | ||
| 2302 | { | ||
| 2303 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(0), "Extension")); | ||
| 2304 | } | ||
| 2305 | } | ||
| 2306 | } | ||
| 2307 | } | ||
| 2308 | |||
| 2309 | /// <summary> | ||
| 2310 | /// Get the path to a file in the source image. | ||
| 2311 | /// </summary> | ||
| 2312 | /// <param name="xFile">The file.</param> | ||
| 2313 | /// <returns>The path to the file in the source image.</returns> | ||
| 2314 | private string GetSourcePath(XElement xFile) | ||
| 2315 | { | ||
| 2316 | var sourcePath = new StringBuilder(); | ||
| 2317 | |||
| 2318 | var component = xFile.Parent; | ||
| 2319 | |||
| 2320 | for (var xDirectory = component.Parent; null != xDirectory && xDirectory.Name.LocalName == "Directory"; xDirectory = xDirectory.Parent) | ||
| 2321 | { | ||
| 2322 | string name; | ||
| 2323 | |||
| 2324 | var dirSourceName = xDirectory.Attribute("SourceName")?.Value; | ||
| 2325 | var dirShortSourceName = xDirectory.Attribute("ShortSourceName")?.Value; | ||
| 2326 | var dirShortName = xDirectory.Attribute("ShortName")?.Value; | ||
| 2327 | var dirName = xDirectory.Attribute("Name")?.Value; | ||
| 2328 | |||
| 2329 | if (!this.ShortNames && null != dirSourceName) | ||
| 2330 | { | ||
| 2331 | name = dirSourceName; | ||
| 2332 | } | ||
| 2333 | else if (null != dirShortSourceName) | ||
| 2334 | { | ||
| 2335 | name = dirShortSourceName; | ||
| 2336 | } | ||
| 2337 | else if (!this.ShortNames || null == dirShortName) | ||
| 2338 | { | ||
| 2339 | name = dirName; | ||
| 2340 | } | ||
| 2341 | else | ||
| 2342 | { | ||
| 2343 | name = dirShortName; | ||
| 2344 | } | ||
| 2345 | |||
| 2346 | if (0 == sourcePath.Length) | ||
| 2347 | { | ||
| 2348 | sourcePath.Append(name); | ||
| 2349 | } | ||
| 2350 | else | ||
| 2351 | { | ||
| 2352 | sourcePath.Insert(0, Path.DirectorySeparatorChar); | ||
| 2353 | sourcePath.Insert(0, name); | ||
| 2354 | } | ||
| 2355 | } | ||
| 2356 | |||
| 2357 | return sourcePath.ToString(); | ||
| 2358 | } | ||
| 2359 | |||
| 2360 | /// <summary> | ||
| 2361 | /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames). | ||
| 2362 | /// </summary> | ||
| 2363 | /// <param name="tableName">The name of the table to resolve.</param> | ||
| 2364 | /// <param name="unsortedTableNames">The unsorted table names.</param> | ||
| 2365 | /// <param name="sortedTableNames">The sorted table names.</param> | ||
| 2366 | private void ResolveTableDependencies(string tableName, List<string> unsortedTableNames, HashSet<string> sortedTableNames) | ||
| 2367 | { | ||
| 2368 | unsortedTableNames.Remove(tableName); | ||
| 2369 | |||
| 2370 | foreach (var columnDefinition in this.TableDefinitions[tableName].Columns) | ||
| 2371 | { | ||
| 2372 | // no dependency to resolve because this column doesn't reference another table | ||
| 2373 | if (null == columnDefinition.KeyTable) | ||
| 2374 | { | ||
| 2375 | continue; | ||
| 2376 | } | ||
| 2377 | |||
| 2378 | foreach (var keyTable in columnDefinition.KeyTable.Split(';')) | ||
| 2379 | { | ||
| 2380 | if (tableName == keyTable) | ||
| 2381 | { | ||
| 2382 | continue; // self-referencing dependency | ||
| 2383 | } | ||
| 2384 | else if (sortedTableNames.Contains(keyTable)) | ||
| 2385 | { | ||
| 2386 | continue; // dependent table has already been sorted | ||
| 2387 | } | ||
| 2388 | else if (!this.TableDefinitions.Contains(keyTable)) | ||
| 2389 | { | ||
| 2390 | this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable)); | ||
| 2391 | } | ||
| 2392 | else if (unsortedTableNames.Contains(keyTable)) | ||
| 2393 | { | ||
| 2394 | this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames); | ||
| 2395 | } | ||
| 2396 | else | ||
| 2397 | { | ||
| 2398 | // found a circular dependency, so ignore it (this assumes that the tables will | ||
| 2399 | // use a finalize method to nest their elements since the ordering will not be | ||
| 2400 | // deterministic | ||
| 2401 | } | ||
| 2402 | } | ||
| 2403 | } | ||
| 2404 | |||
| 2405 | sortedTableNames.Add(tableName); | ||
| 2406 | } | ||
| 2407 | |||
| 2408 | /// <summary> | ||
| 2409 | /// Get the names of the tables to process in the order they should be processed, according to their dependencies. | ||
| 2410 | /// </summary> | ||
| 2411 | /// <returns>A StringCollection containing the ordered table names.</returns> | ||
| 2412 | private HashSet<string> GetOrderedTableNames() | ||
| 2413 | { | ||
| 2414 | var orderedTableNames = new HashSet<string>(); | ||
| 2415 | var unsortedTableNames = new List<string>(this.TableDefinitions.Select(t => t.Name)); | ||
| 2416 | |||
| 2417 | // resolve the dependencies for each table | ||
| 2418 | while (0 < unsortedTableNames.Count) | ||
| 2419 | { | ||
| 2420 | this.ResolveTableDependencies(unsortedTableNames[0], unsortedTableNames, orderedTableNames); | ||
| 2421 | } | ||
| 2422 | |||
| 2423 | return orderedTableNames; | ||
| 2424 | } | ||
| 2425 | |||
| 2426 | /// <summary> | ||
| 2427 | /// Initialize decompilation. | ||
| 2428 | /// </summary> | ||
| 2429 | /// <param name="tables">The collection of all tables.</param> | ||
| 2430 | /// <param name="codepage"></param> | ||
| 2431 | private void InitializeDecompile(TableIndexedCollection tables, int codepage) | ||
| 2432 | { | ||
| 2433 | // reset all the state information | ||
| 2434 | this.Compressed = false; | ||
| 2435 | this.ShortNames = false; | ||
| 2436 | |||
| 2437 | this.Singletons.Clear(); | ||
| 2438 | this.IndexedElements.Clear(); | ||
| 2439 | this.PatchTargetFiles.Clear(); | ||
| 2440 | |||
| 2441 | // set the codepage if its not neutral (0) | ||
| 2442 | if (0 != codepage) | ||
| 2443 | { | ||
| 2444 | this.RootElement.SetAttributeValue("Codepage", codepage); | ||
| 2445 | } | ||
| 2446 | |||
| 2447 | if (this.OutputType == OutputType.Module) | ||
| 2448 | { | ||
| 2449 | var table = tables["_SummaryInformation"]; | ||
| 2450 | var row = table.Rows.SingleOrDefault(r => r.FieldAsInteger(0) == 9); | ||
| 2451 | this.ModularizationGuid = row?.FieldAsString(1); | ||
| 2452 | } | ||
| 2453 | |||
| 2454 | // index the rows from the extension libraries | ||
| 2455 | var indexedExtensionTables = new Dictionary<string, HashSet<string>>(); | ||
| 2456 | #if TODO_DECOMPILER_EXTENSIONS | ||
| 2457 | foreach (IDecompilerExtension extension in this.extensions) | ||
| 2458 | { | ||
| 2459 | // Get the optional library from the extension with the rows to be removed. | ||
| 2460 | Library library = extension.GetLibraryToRemove(this.tableDefinitions); | ||
| 2461 | if (null != library) | ||
| 2462 | { | ||
| 2463 | foreach (var section in library.Sections) | ||
| 2464 | { | ||
| 2465 | foreach (Table table in section.Tables) | ||
| 2466 | { | ||
| 2467 | foreach (Row row in table.Rows) | ||
| 2468 | { | ||
| 2469 | string primaryKey; | ||
| 2470 | string tableName; | ||
| 2471 | |||
| 2472 | // the Actions table needs to be handled specially | ||
| 2473 | if ("WixAction" == table.Name) | ||
| 2474 | { | ||
| 2475 | primaryKey = row.FieldAsString(1); | ||
| 2476 | |||
| 2477 | if (OutputType.Module == this.outputType) | ||
| 2478 | { | ||
| 2479 | tableName = String.Concat("Module", row.FieldAsString(0)); | ||
| 2480 | } | ||
| 2481 | else | ||
| 2482 | { | ||
| 2483 | tableName = row.FieldAsString(0); | ||
| 2484 | } | ||
| 2485 | } | ||
| 2486 | else | ||
| 2487 | { | ||
| 2488 | primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter); | ||
| 2489 | tableName = table.Name; | ||
| 2490 | } | ||
| 2491 | |||
| 2492 | if (null != primaryKey) | ||
| 2493 | { | ||
| 2494 | HashSet<string> indexedExtensionRows; | ||
| 2495 | if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows)) | ||
| 2496 | { | ||
| 2497 | indexedExtensionRows = new HashSet<string>(); | ||
| 2498 | indexedExtensionTables.Add(tableName, indexedExtensionRows); | ||
| 2499 | } | ||
| 2500 | |||
| 2501 | indexedExtensionRows.Add(primaryKey); | ||
| 2502 | } | ||
| 2503 | } | ||
| 2504 | } | ||
| 2505 | } | ||
| 2506 | } | ||
| 2507 | } | ||
| 2508 | #endif | ||
| 2509 | |||
| 2510 | // remove the rows from the extension libraries (to allow full round-tripping) | ||
| 2511 | foreach (var kvp in indexedExtensionTables) | ||
| 2512 | { | ||
| 2513 | var tableName = kvp.Key; | ||
| 2514 | var indexedExtensionRows = kvp.Value; | ||
| 2515 | |||
| 2516 | var table = tables[tableName]; | ||
| 2517 | if (null != table) | ||
| 2518 | { | ||
| 2519 | var originalRows = new RowDictionary<Row>(table); | ||
| 2520 | |||
| 2521 | // remove the original rows so that they can be added back if they should remain | ||
| 2522 | table.Rows.Clear(); | ||
| 2523 | |||
| 2524 | foreach (var row in originalRows.Values) | ||
| 2525 | { | ||
| 2526 | if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter))) | ||
| 2527 | { | ||
| 2528 | table.Rows.Add(row); | ||
| 2529 | } | ||
| 2530 | } | ||
| 2531 | } | ||
| 2532 | } | ||
| 2533 | } | ||
| 2534 | |||
| 2535 | /// <summary> | ||
| 2536 | /// Decompile the tables. | ||
| 2537 | /// </summary> | ||
| 2538 | /// <param name="output">The output being decompiled.</param> | ||
| 2539 | private void DecompileTables(WindowsInstallerData output) | ||
| 2540 | { | ||
| 2541 | var orderedTableNames = this.GetOrderedTableNames(); | ||
| 2542 | foreach (var tableName in orderedTableNames) | ||
| 2543 | { | ||
| 2544 | var table = output.Tables[tableName]; | ||
| 2545 | |||
| 2546 | // table does not exist in this database or should not be decompiled | ||
| 2547 | if (null == table || !this.DecompilableTable(output, tableName)) | ||
| 2548 | { | ||
| 2549 | continue; | ||
| 2550 | } | ||
| 2551 | |||
| 2552 | this.Messaging.Write(VerboseMessages.DecompilingTable(table.Name)); | ||
| 2553 | |||
| 2554 | // empty tables may be kept with EnsureTable if the user set the proper option | ||
| 2555 | if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables) | ||
| 2556 | { | ||
| 2557 | this.RootElement.Add(new XElement(Names.EnsureTableElement, new XAttribute("Id", table.Name))); | ||
| 2558 | } | ||
| 2559 | |||
| 2560 | switch (table.Name) | ||
| 2561 | { | ||
| 2562 | case "_SummaryInformation": | ||
| 2563 | // handled in FinalizeDecompile | ||
| 2564 | break; | ||
| 2565 | case "AdminExecuteSequence": | ||
| 2566 | case "AdminUISequence": | ||
| 2567 | case "AdvtExecuteSequence": | ||
| 2568 | case "InstallExecuteSequence": | ||
| 2569 | case "InstallUISequence": | ||
| 2570 | case "ModuleAdminExecuteSequence": | ||
| 2571 | case "ModuleAdminUISequence": | ||
| 2572 | case "ModuleAdvtExecuteSequence": | ||
| 2573 | case "ModuleInstallExecuteSequence": | ||
| 2574 | case "ModuleInstallUISequence": | ||
| 2575 | // handled in FinalizeSequenceTables | ||
| 2576 | break; | ||
| 2577 | case "ActionText": | ||
| 2578 | this.DecompileActionTextTable(table); | ||
| 2579 | break; | ||
| 2580 | case "AdvtUISequence": | ||
| 2581 | this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); | ||
| 2582 | break; | ||
| 2583 | case "AppId": | ||
| 2584 | this.DecompileAppIdTable(table); | ||
| 2585 | break; | ||
| 2586 | case "AppSearch": | ||
| 2587 | // handled in FinalizeSearchTables | ||
| 2588 | break; | ||
| 2589 | case "BBControl": | ||
| 2590 | this.DecompileBBControlTable(table); | ||
| 2591 | break; | ||
| 2592 | case "Billboard": | ||
| 2593 | this.DecompileBillboardTable(table); | ||
| 2594 | break; | ||
| 2595 | case "Binary": | ||
| 2596 | this.DecompileBinaryTable(table); | ||
| 2597 | break; | ||
| 2598 | case "BindImage": | ||
| 2599 | this.DecompileBindImageTable(table); | ||
| 2600 | break; | ||
| 2601 | case "CCPSearch": | ||
| 2602 | // handled in FinalizeSearchTables | ||
| 2603 | break; | ||
| 2604 | case "CheckBox": | ||
| 2605 | // handled in FinalizeCheckBoxTable | ||
| 2606 | break; | ||
| 2607 | case "Class": | ||
| 2608 | this.DecompileClassTable(table); | ||
| 2609 | break; | ||
| 2610 | case "ComboBox": | ||
| 2611 | this.DecompileComboBoxTable(table); | ||
| 2612 | break; | ||
| 2613 | case "Control": | ||
| 2614 | this.DecompileControlTable(table); | ||
| 2615 | break; | ||
| 2616 | case "ControlCondition": | ||
| 2617 | this.DecompileControlConditionTable(table); | ||
| 2618 | break; | ||
| 2619 | case "ControlEvent": | ||
| 2620 | this.DecompileControlEventTable(table); | ||
| 2621 | break; | ||
| 2622 | case "CreateFolder": | ||
| 2623 | this.DecompileCreateFolderTable(table); | ||
| 2624 | break; | ||
| 2625 | case "CustomAction": | ||
| 2626 | this.DecompileCustomActionTable(table); | ||
| 2627 | break; | ||
| 2628 | case "CompLocator": | ||
| 2629 | this.DecompileCompLocatorTable(table); | ||
| 2630 | break; | ||
| 2631 | case "Complus": | ||
| 2632 | this.DecompileComplusTable(table); | ||
| 2633 | break; | ||
| 2634 | case "Component": | ||
| 2635 | this.DecompileComponentTable(table); | ||
| 2636 | break; | ||
| 2637 | case "Condition": | ||
| 2638 | this.DecompileConditionTable(table); | ||
| 2639 | break; | ||
| 2640 | case "Dialog": | ||
| 2641 | this.DecompileDialogTable(table); | ||
| 2642 | break; | ||
| 2643 | case "Directory": | ||
| 2644 | this.DecompileDirectoryTable(table); | ||
| 2645 | break; | ||
| 2646 | case "DrLocator": | ||
| 2647 | this.DecompileDrLocatorTable(table); | ||
| 2648 | break; | ||
| 2649 | case "DuplicateFile": | ||
| 2650 | this.DecompileDuplicateFileTable(table); | ||
| 2651 | break; | ||
| 2652 | case "Environment": | ||
| 2653 | this.DecompileEnvironmentTable(table); | ||
| 2654 | break; | ||
| 2655 | case "Error": | ||
| 2656 | this.DecompileErrorTable(table); | ||
| 2657 | break; | ||
| 2658 | case "EventMapping": | ||
| 2659 | this.DecompileEventMappingTable(table); | ||
| 2660 | break; | ||
| 2661 | case "Extension": | ||
| 2662 | this.DecompileExtensionTable(table); | ||
| 2663 | break; | ||
| 2664 | case "ExternalFiles": | ||
| 2665 | this.DecompileExternalFilesTable(table); | ||
| 2666 | break; | ||
| 2667 | case "FamilyFileRanges": | ||
| 2668 | // handled in FinalizeFamilyFileRangesTable | ||
| 2669 | break; | ||
| 2670 | case "Feature": | ||
| 2671 | this.DecompileFeatureTable(table); | ||
| 2672 | break; | ||
| 2673 | case "FeatureComponents": | ||
| 2674 | this.DecompileFeatureComponentsTable(table); | ||
| 2675 | break; | ||
| 2676 | case "File": | ||
| 2677 | this.DecompileFileTable(table); | ||
| 2678 | break; | ||
| 2679 | case "FileSFPCatalog": | ||
| 2680 | this.DecompileFileSFPCatalogTable(table); | ||
| 2681 | break; | ||
| 2682 | case "Font": | ||
| 2683 | this.DecompileFontTable(table); | ||
| 2684 | break; | ||
| 2685 | case "Icon": | ||
| 2686 | this.DecompileIconTable(table); | ||
| 2687 | break; | ||
| 2688 | case "ImageFamilies": | ||
| 2689 | this.DecompileImageFamiliesTable(table); | ||
| 2690 | break; | ||
| 2691 | case "IniFile": | ||
| 2692 | this.DecompileIniFileTable(table); | ||
| 2693 | break; | ||
| 2694 | case "IniLocator": | ||
| 2695 | this.DecompileIniLocatorTable(table); | ||
| 2696 | break; | ||
| 2697 | case "IsolatedComponent": | ||
| 2698 | this.DecompileIsolatedComponentTable(table); | ||
| 2699 | break; | ||
| 2700 | case "LaunchCondition": | ||
| 2701 | this.DecompileLaunchConditionTable(table); | ||
| 2702 | break; | ||
| 2703 | case "ListBox": | ||
| 2704 | this.DecompileListBoxTable(table); | ||
| 2705 | break; | ||
| 2706 | case "ListView": | ||
| 2707 | this.DecompileListViewTable(table); | ||
| 2708 | break; | ||
| 2709 | case "LockPermissions": | ||
| 2710 | this.DecompileLockPermissionsTable(table); | ||
| 2711 | break; | ||
| 2712 | case "Media": | ||
| 2713 | this.DecompileMediaTable(table); | ||
| 2714 | break; | ||
| 2715 | case "MIME": | ||
| 2716 | this.DecompileMIMETable(table); | ||
| 2717 | break; | ||
| 2718 | case "ModuleAdvtUISequence": | ||
| 2719 | this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); | ||
| 2720 | break; | ||
| 2721 | case "ModuleComponents": | ||
| 2722 | // handled by DecompileComponentTable (since the ModuleComponents table | ||
| 2723 | // rows are created by nesting components under the Module element) | ||
| 2724 | break; | ||
| 2725 | case "ModuleConfiguration": | ||
| 2726 | this.DecompileModuleConfigurationTable(table); | ||
| 2727 | break; | ||
| 2728 | case "ModuleDependency": | ||
| 2729 | this.DecompileModuleDependencyTable(table); | ||
| 2730 | break; | ||
| 2731 | case "ModuleExclusion": | ||
| 2732 | this.DecompileModuleExclusionTable(table); | ||
| 2733 | break; | ||
| 2734 | case "ModuleIgnoreTable": | ||
| 2735 | this.DecompileModuleIgnoreTableTable(table); | ||
| 2736 | break; | ||
| 2737 | case "ModuleSignature": | ||
| 2738 | this.DecompileModuleSignatureTable(table); | ||
| 2739 | break; | ||
| 2740 | case "ModuleSubstitution": | ||
| 2741 | this.DecompileModuleSubstitutionTable(table); | ||
| 2742 | break; | ||
| 2743 | case "MoveFile": | ||
| 2744 | this.DecompileMoveFileTable(table); | ||
| 2745 | break; | ||
| 2746 | case "MsiAssembly": | ||
| 2747 | // handled in FinalizeFileTable | ||
| 2748 | break; | ||
| 2749 | case "MsiDigitalCertificate": | ||
| 2750 | this.DecompileMsiDigitalCertificateTable(table); | ||
| 2751 | break; | ||
| 2752 | case "MsiDigitalSignature": | ||
| 2753 | this.DecompileMsiDigitalSignatureTable(table); | ||
| 2754 | break; | ||
| 2755 | case "MsiEmbeddedChainer": | ||
| 2756 | this.DecompileMsiEmbeddedChainerTable(table); | ||
| 2757 | break; | ||
| 2758 | case "MsiEmbeddedUI": | ||
| 2759 | this.DecompileMsiEmbeddedUITable(table); | ||
| 2760 | break; | ||
| 2761 | case "MsiLockPermissionsEx": | ||
| 2762 | this.DecompileMsiLockPermissionsExTable(table); | ||
| 2763 | break; | ||
| 2764 | case "MsiPackageCertificate": | ||
| 2765 | this.DecompileMsiPackageCertificateTable(table); | ||
| 2766 | break; | ||
| 2767 | case "MsiPatchCertificate": | ||
| 2768 | this.DecompileMsiPatchCertificateTable(table); | ||
| 2769 | break; | ||
| 2770 | case "MsiShortcutProperty": | ||
| 2771 | this.DecompileMsiShortcutPropertyTable(table); | ||
| 2772 | break; | ||
| 2773 | case "ODBCAttribute": | ||
| 2774 | this.DecompileODBCAttributeTable(table); | ||
| 2775 | break; | ||
| 2776 | case "ODBCDataSource": | ||
| 2777 | this.DecompileODBCDataSourceTable(table); | ||
| 2778 | break; | ||
| 2779 | case "ODBCDriver": | ||
| 2780 | this.DecompileODBCDriverTable(table); | ||
| 2781 | break; | ||
| 2782 | case "ODBCSourceAttribute": | ||
| 2783 | this.DecompileODBCSourceAttributeTable(table); | ||
| 2784 | break; | ||
| 2785 | case "ODBCTranslator": | ||
| 2786 | this.DecompileODBCTranslatorTable(table); | ||
| 2787 | break; | ||
| 2788 | case "PatchMetadata": | ||
| 2789 | this.DecompilePatchMetadataTable(table); | ||
| 2790 | break; | ||
| 2791 | case "PatchSequence": | ||
| 2792 | this.DecompilePatchSequenceTable(table); | ||
| 2793 | break; | ||
| 2794 | case "ProgId": | ||
| 2795 | this.DecompileProgIdTable(table); | ||
| 2796 | break; | ||
| 2797 | case "Properties": | ||
| 2798 | this.DecompilePropertiesTable(table); | ||
| 2799 | break; | ||
| 2800 | case "Property": | ||
| 2801 | this.DecompilePropertyTable(table); | ||
| 2802 | break; | ||
| 2803 | case "PublishComponent": | ||
| 2804 | this.DecompilePublishComponentTable(table); | ||
| 2805 | break; | ||
| 2806 | case "RadioButton": | ||
| 2807 | this.DecompileRadioButtonTable(table); | ||
| 2808 | break; | ||
| 2809 | case "Registry": | ||
| 2810 | this.DecompileRegistryTable(table); | ||
| 2811 | break; | ||
| 2812 | case "RegLocator": | ||
| 2813 | this.DecompileRegLocatorTable(table); | ||
| 2814 | break; | ||
| 2815 | case "RemoveFile": | ||
| 2816 | this.DecompileRemoveFileTable(table); | ||
| 2817 | break; | ||
| 2818 | case "RemoveIniFile": | ||
| 2819 | this.DecompileRemoveIniFileTable(table); | ||
| 2820 | break; | ||
| 2821 | case "RemoveRegistry": | ||
| 2822 | this.DecompileRemoveRegistryTable(table); | ||
| 2823 | break; | ||
| 2824 | case "ReserveCost": | ||
| 2825 | this.DecompileReserveCostTable(table); | ||
| 2826 | break; | ||
| 2827 | case "SelfReg": | ||
| 2828 | this.DecompileSelfRegTable(table); | ||
| 2829 | break; | ||
| 2830 | case "ServiceControl": | ||
| 2831 | this.DecompileServiceControlTable(table); | ||
| 2832 | break; | ||
| 2833 | case "ServiceInstall": | ||
| 2834 | this.DecompileServiceInstallTable(table); | ||
| 2835 | break; | ||
| 2836 | case "SFPCatalog": | ||
| 2837 | this.DecompileSFPCatalogTable(table); | ||
| 2838 | break; | ||
| 2839 | case "Shortcut": | ||
| 2840 | this.DecompileShortcutTable(table); | ||
| 2841 | break; | ||
| 2842 | case "Signature": | ||
| 2843 | this.DecompileSignatureTable(table); | ||
| 2844 | break; | ||
| 2845 | case "TargetFiles_OptionalData": | ||
| 2846 | this.DecompileTargetFiles_OptionalDataTable(table); | ||
| 2847 | break; | ||
| 2848 | case "TargetImages": | ||
| 2849 | this.DecompileTargetImagesTable(table); | ||
| 2850 | break; | ||
| 2851 | case "TextStyle": | ||
| 2852 | this.DecompileTextStyleTable(table); | ||
| 2853 | break; | ||
| 2854 | case "TypeLib": | ||
| 2855 | this.DecompileTypeLibTable(table); | ||
| 2856 | break; | ||
| 2857 | case "Upgrade": | ||
| 2858 | this.DecompileUpgradeTable(table); | ||
| 2859 | break; | ||
| 2860 | case "UpgradedFiles_OptionalData": | ||
| 2861 | this.DecompileUpgradedFiles_OptionalDataTable(table); | ||
| 2862 | break; | ||
| 2863 | case "UpgradedFilesToIgnore": | ||
| 2864 | this.DecompileUpgradedFilesToIgnoreTable(table); | ||
| 2865 | break; | ||
| 2866 | case "UpgradedImages": | ||
| 2867 | this.DecompileUpgradedImagesTable(table); | ||
| 2868 | break; | ||
| 2869 | case "UIText": | ||
| 2870 | this.DecompileUITextTable(table); | ||
| 2871 | break; | ||
| 2872 | case "Verb": | ||
| 2873 | this.DecompileVerbTable(table); | ||
| 2874 | break; | ||
| 2875 | |||
| 2876 | default: | ||
| 2877 | #if TODO_DECOMPILER_EXTENSIONS | ||
| 2878 | if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension) | ||
| 2879 | { | ||
| 2880 | extension.DecompileTable(table); | ||
| 2881 | } | ||
| 2882 | else | ||
| 2883 | #endif | ||
| 2884 | if (!this.SuppressCustomTables) | ||
| 2885 | { | ||
| 2886 | this.DecompileCustomTable(table); | ||
| 2887 | } | ||
| 2888 | break; | ||
| 2889 | } | ||
| 2890 | } | ||
| 2891 | } | ||
| 2892 | |||
| 2893 | /// <summary> | ||
| 2894 | /// Determine if a particular table should be decompiled with the current settings. | ||
| 2895 | /// </summary> | ||
| 2896 | /// <param name="output">The output being decompiled.</param> | ||
| 2897 | /// <param name="tableName">The name of a table.</param> | ||
| 2898 | /// <returns>true if the table should be decompiled; false otherwise.</returns> | ||
| 2899 | private bool DecompilableTable(WindowsInstallerData output, string tableName) | ||
| 2900 | { | ||
| 2901 | switch (tableName) | ||
| 2902 | { | ||
| 2903 | case "ActionText": | ||
| 2904 | case "BBControl": | ||
| 2905 | case "Billboard": | ||
| 2906 | case "CheckBox": | ||
| 2907 | case "Control": | ||
| 2908 | case "ControlCondition": | ||
| 2909 | case "ControlEvent": | ||
| 2910 | case "Dialog": | ||
| 2911 | case "Error": | ||
| 2912 | case "EventMapping": | ||
| 2913 | case "RadioButton": | ||
| 2914 | case "TextStyle": | ||
| 2915 | case "UIText": | ||
| 2916 | return !this.SuppressUI; | ||
| 2917 | case "ModuleAdminExecuteSequence": | ||
| 2918 | case "ModuleAdminUISequence": | ||
| 2919 | case "ModuleAdvtExecuteSequence": | ||
| 2920 | case "ModuleAdvtUISequence": | ||
| 2921 | case "ModuleComponents": | ||
| 2922 | case "ModuleConfiguration": | ||
| 2923 | case "ModuleDependency": | ||
| 2924 | case "ModuleIgnoreTable": | ||
| 2925 | case "ModuleInstallExecuteSequence": | ||
| 2926 | case "ModuleInstallUISequence": | ||
| 2927 | case "ModuleExclusion": | ||
| 2928 | case "ModuleSignature": | ||
| 2929 | case "ModuleSubstitution": | ||
| 2930 | if (OutputType.Module != output.Type) | ||
| 2931 | { | ||
| 2932 | this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName)); | ||
| 2933 | return false; | ||
| 2934 | } | ||
| 2935 | else | ||
| 2936 | { | ||
| 2937 | return true; | ||
| 2938 | } | ||
| 2939 | case "ExternalFiles": | ||
| 2940 | case "FamilyFileRanges": | ||
| 2941 | case "ImageFamilies": | ||
| 2942 | case "PatchMetadata": | ||
| 2943 | case "PatchSequence": | ||
| 2944 | case "Properties": | ||
| 2945 | case "TargetFiles_OptionalData": | ||
| 2946 | case "TargetImages": | ||
| 2947 | case "UpgradedFiles_OptionalData": | ||
| 2948 | case "UpgradedFilesToIgnore": | ||
| 2949 | case "UpgradedImages": | ||
| 2950 | if (OutputType.PatchCreation != output.Type) | ||
| 2951 | { | ||
| 2952 | this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName)); | ||
| 2953 | return false; | ||
| 2954 | } | ||
| 2955 | else | ||
| 2956 | { | ||
| 2957 | return true; | ||
| 2958 | } | ||
| 2959 | case "MsiPatchHeaders": | ||
| 2960 | case "MsiPatchMetadata": | ||
| 2961 | case "MsiPatchOldAssemblyName": | ||
| 2962 | case "MsiPatchOldAssemblyFile": | ||
| 2963 | case "MsiPatchSequence": | ||
| 2964 | case "Patch": | ||
| 2965 | case "PatchPackage": | ||
| 2966 | this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName)); | ||
| 2967 | return false; | ||
| 2968 | case "_SummaryInformation": | ||
| 2969 | return true; | ||
| 2970 | case "_Validation": | ||
| 2971 | case "MsiAssemblyName": | ||
| 2972 | case "MsiFileHash": | ||
| 2973 | return false; | ||
| 2974 | default: // all other tables are allowed in any output except for a patch creation package | ||
| 2975 | if (OutputType.PatchCreation == output.Type) | ||
| 2976 | { | ||
| 2977 | this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName)); | ||
| 2978 | return false; | ||
| 2979 | } | ||
| 2980 | else | ||
| 2981 | { | ||
| 2982 | return true; | ||
| 2983 | } | ||
| 2984 | } | ||
| 2985 | } | ||
| 2986 | |||
| 2987 | /// <summary> | ||
| 2988 | /// Decompile the _SummaryInformation table. | ||
| 2989 | /// </summary> | ||
| 2990 | /// <param name="tables">The tables to decompile.</param> | ||
| 2991 | private void FinalizeSummaryInformationStream(TableIndexedCollection tables) | ||
| 2992 | { | ||
| 2993 | var table = tables["_SummaryInformation"]; | ||
| 2994 | |||
| 2995 | if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType) | ||
| 2996 | { | ||
| 2997 | var xSummaryInformation = new XElement(Names.SummaryInformationElement); | ||
| 2998 | |||
| 2999 | foreach (var row in table.Rows) | ||
| 3000 | { | ||
| 3001 | var value = row.FieldAsString(1); | ||
| 3002 | |||
| 3003 | if (!String.IsNullOrEmpty(value)) | ||
| 3004 | { | ||
| 3005 | switch (row.FieldAsInteger(0)) | ||
| 3006 | { | ||
| 3007 | case 1: | ||
| 3008 | if ("1252" != value) | ||
| 3009 | { | ||
| 3010 | xSummaryInformation.SetAttributeValue("Codepage", value); | ||
| 3011 | } | ||
| 3012 | break; | ||
| 3013 | case 3: | ||
| 3014 | { | ||
| 3015 | var productName = this.RootElement.Attribute("Name")?.Value; | ||
| 3016 | if (value != productName) | ||
| 3017 | { | ||
| 3018 | xSummaryInformation.SetAttributeValue("Description", value); | ||
| 3019 | } | ||
| 3020 | break; | ||
| 3021 | } | ||
| 3022 | case 4: | ||
| 3023 | { | ||
| 3024 | var productManufacturer = this.RootElement.Attribute("Manufacturer")?.Value; | ||
| 3025 | if (value != productManufacturer) | ||
| 3026 | { | ||
| 3027 | xSummaryInformation.SetAttributeValue("Manufacturer", value); | ||
| 3028 | } | ||
| 3029 | break; | ||
| 3030 | } | ||
| 3031 | case 5: | ||
| 3032 | if ("Installer" != value) | ||
| 3033 | { | ||
| 3034 | xSummaryInformation.SetAttributeValue("Keywords", value); | ||
| 3035 | } | ||
| 3036 | break; | ||
| 3037 | case 7: | ||
| 3038 | var template = value.Split(';'); | ||
| 3039 | if (0 < template.Length && 0 < template[template.Length - 1].Length) | ||
| 3040 | { | ||
| 3041 | this.RootElement.SetAttributeValue("Language", template[template.Length - 1]); | ||
| 3042 | } | ||
| 3043 | break; | ||
| 3044 | case 14: | ||
| 3045 | var installerVersion = row.FieldAsInteger(1); | ||
| 3046 | // Default InstallerVersion. | ||
| 3047 | if (installerVersion != 500) | ||
| 3048 | { | ||
| 3049 | this.RootElement.SetAttributeValue("InstallerVersion", installerVersion); | ||
| 3050 | } | ||
| 3051 | break; | ||
| 3052 | case 15: | ||
| 3053 | var wordCount = row.FieldAsInteger(1); | ||
| 3054 | if (0x1 == (wordCount & 0x1)) | ||
| 3055 | { | ||
| 3056 | this.ShortNames = true; | ||
| 3057 | if (OutputType.Product == this.OutputType) | ||
| 3058 | { | ||
| 3059 | this.RootElement.SetAttributeValue("ShortNames", "yes"); | ||
| 3060 | } | ||
| 3061 | } | ||
| 3062 | |||
| 3063 | if (0x2 == (wordCount & 0x2)) | ||
| 3064 | { | ||
| 3065 | this.Compressed = true; | ||
| 3066 | |||
| 3067 | if (OutputType.Product == this.OutputType) | ||
| 3068 | { | ||
| 3069 | this.RootElement.SetAttributeValue("Compressed", "yes"); | ||
| 3070 | } | ||
| 3071 | } | ||
| 3072 | |||
| 3073 | if (OutputType.Product == this.OutputType) | ||
| 3074 | { | ||
| 3075 | if (0x8 == (wordCount & 0x8)) | ||
| 3076 | { | ||
| 3077 | this.RootElement.SetAttributeValue("Scope", "perUser"); | ||
| 3078 | } | ||
| 3079 | else | ||
| 3080 | { | ||
| 3081 | var xAllUsers = this.RootElement.Elements(Names.PropertyElement).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS"); | ||
| 3082 | if (xAllUsers?.Attribute("Value")?.Value == "1") | ||
| 3083 | { | ||
| 3084 | xAllUsers?.Remove(); | ||
| 3085 | } | ||
| 3086 | } | ||
| 3087 | } | ||
| 3088 | |||
| 3089 | break; | ||
| 3090 | } | ||
| 3091 | } | ||
| 3092 | } | ||
| 3093 | |||
| 3094 | if (xSummaryInformation.HasAttributes) | ||
| 3095 | { | ||
| 3096 | this.RootElement.Add(xSummaryInformation); | ||
| 3097 | } | ||
| 3098 | } | ||
| 3099 | else | ||
| 3100 | { | ||
| 3101 | var xPatchInformation = new XElement(Names.PatchInformationElement); | ||
| 3102 | |||
| 3103 | foreach (var row in table.Rows) | ||
| 3104 | { | ||
| 3105 | var propertyId = row.FieldAsInteger(0); | ||
| 3106 | var value = row.FieldAsString(1); | ||
| 3107 | |||
| 3108 | if (!String.IsNullOrEmpty(value)) | ||
| 3109 | { | ||
| 3110 | switch (propertyId) | ||
| 3111 | { | ||
| 3112 | case 1: | ||
| 3113 | if ("1252" != value) | ||
| 3114 | { | ||
| 3115 | xPatchInformation.SetAttributeValue("SummaryCodepage", value); | ||
| 3116 | } | ||
| 3117 | break; | ||
| 3118 | case 3: | ||
| 3119 | xPatchInformation.SetAttributeValue("Description", value); | ||
| 3120 | break; | ||
| 3121 | case 4: | ||
| 3122 | xPatchInformation.SetAttributeValue("Manufacturer", value); | ||
| 3123 | break; | ||
| 3124 | case 5: | ||
| 3125 | if ("Installer,Patching,PCP,Database" != value) | ||
| 3126 | { | ||
| 3127 | xPatchInformation.SetAttributeValue("Keywords", value); | ||
| 3128 | } | ||
| 3129 | break; | ||
| 3130 | case 6: | ||
| 3131 | xPatchInformation.SetAttributeValue("Comments", value); | ||
| 3132 | break; | ||
| 3133 | case 19: | ||
| 3134 | var security = Convert.ToInt32(value, CultureInfo.InvariantCulture); | ||
| 3135 | switch (security) | ||
| 3136 | { | ||
| 3137 | case 0: | ||
| 3138 | xPatchInformation.SetAttributeValue("ReadOnly", "no"); | ||
| 3139 | break; | ||
| 3140 | case 4: | ||
| 3141 | xPatchInformation.SetAttributeValue("ReadOnly", "yes"); | ||
| 3142 | break; | ||
| 3143 | } | ||
| 3144 | break; | ||
| 3145 | } | ||
| 3146 | } | ||
| 3147 | } | ||
| 3148 | |||
| 3149 | this.RootElement.Add(xPatchInformation); | ||
| 3150 | } | ||
| 3151 | } | ||
| 3152 | |||
| 3153 | /// <summary> | ||
| 3154 | /// Decompile the ActionText table. | ||
| 3155 | /// </summary> | ||
| 3156 | /// <param name="table">The table to decompile.</param> | ||
| 3157 | private void DecompileActionTextTable(Table table) | ||
| 3158 | { | ||
| 3159 | foreach (var row in table.Rows) | ||
| 3160 | { | ||
| 3161 | var progressText = new XElement(Names.ProgressTextElement, | ||
| 3162 | new XAttribute("Action", row.FieldAsString(0)), | ||
| 3163 | row.IsColumnNull(1) ? null : new XAttribute("Message", row.FieldAsString(1)), | ||
| 3164 | row.IsColumnNull(2) ? null : new XAttribute("Template", row.FieldAsString(2))); | ||
| 3165 | |||
| 3166 | this.UIElement.Add(progressText); | ||
| 3167 | } | ||
| 3168 | } | ||
| 3169 | |||
| 3170 | /// <summary> | ||
| 3171 | /// Decompile the AppId table. | ||
| 3172 | /// </summary> | ||
| 3173 | /// <param name="table">The table to decompile.</param> | ||
| 3174 | private void DecompileAppIdTable(Table table) | ||
| 3175 | { | ||
| 3176 | foreach (var row in table.Rows) | ||
| 3177 | { | ||
| 3178 | var appId = new XElement(Names.AppIdElement, | ||
| 3179 | new XAttribute("Advertise", "yes"), | ||
| 3180 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 3181 | row.IsColumnNull(1) ? null : new XAttribute("RemoteServerName", row.FieldAsString(1)), | ||
| 3182 | row.IsColumnNull(2) ? null : new XAttribute("LocalService", row.FieldAsString(2)), | ||
| 3183 | row.IsColumnNull(3) ? null : new XAttribute("ServiceParameters", row.FieldAsString(3)), | ||
| 3184 | row.IsColumnNull(4) ? null : new XAttribute("DllSurrogate", row.FieldAsString(4)), | ||
| 3185 | row.IsColumnNull(5) || row.FieldAsInteger(5) != 1 ? null : new XAttribute("ActivateAtStorage", "yes"), | ||
| 3186 | row.IsColumnNull(6) || row.FieldAsInteger(6) != 1 ? null : new XAttribute("RunAsInteractiveUser", "yes")); | ||
| 3187 | |||
| 3188 | this.RootElement.Add(appId); | ||
| 3189 | this.IndexElement(row, appId); | ||
| 3190 | } | ||
| 3191 | } | ||
| 3192 | |||
| 3193 | /// <summary> | ||
| 3194 | /// Decompile the BBControl table. | ||
| 3195 | /// </summary> | ||
| 3196 | /// <param name="table">The table to decompile.</param> | ||
| 3197 | private void DecompileBBControlTable(Table table) | ||
| 3198 | { | ||
| 3199 | foreach (BBControlRow bbControlRow in table.Rows) | ||
| 3200 | { | ||
| 3201 | var xControl = new XElement(Names.ControlElement, | ||
| 3202 | new XAttribute("Id", bbControlRow.BBControl), | ||
| 3203 | new XAttribute("Type", bbControlRow.Type), | ||
| 3204 | new XAttribute("X", bbControlRow.X), | ||
| 3205 | new XAttribute("Y", bbControlRow.Y), | ||
| 3206 | new XAttribute("Width", bbControlRow.Width), | ||
| 3207 | new XAttribute("Height", bbControlRow.Height), | ||
| 3208 | null == bbControlRow.Text ? null : new XAttribute("Text", bbControlRow.Text)); | ||
| 3209 | |||
| 3210 | if (null != bbControlRow[7]) | ||
| 3211 | { | ||
| 3212 | SetControlAttributes(bbControlRow.Attributes, xControl); | ||
| 3213 | } | ||
| 3214 | |||
| 3215 | if (this.TryGetIndexedElement("Billboard", out var xBillboard, bbControlRow.Billboard)) | ||
| 3216 | { | ||
| 3217 | xBillboard.Add(xControl); | ||
| 3218 | } | ||
| 3219 | else | ||
| 3220 | { | ||
| 3221 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard")); | ||
| 3222 | } | ||
| 3223 | } | ||
| 3224 | } | ||
| 3225 | |||
| 3226 | /// <summary> | ||
| 3227 | /// Decompile the Billboard table. | ||
| 3228 | /// </summary> | ||
| 3229 | /// <param name="table">The table to decompile.</param> | ||
| 3230 | private void DecompileBillboardTable(Table table) | ||
| 3231 | { | ||
| 3232 | var billboards = new SortedList<string, Row>(); | ||
| 3233 | |||
| 3234 | foreach (var row in table.Rows) | ||
| 3235 | { | ||
| 3236 | var xBillboard = new XElement(Names.BillboardElement, | ||
| 3237 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 3238 | new XAttribute("Feature", row.FieldAsString(1))); | ||
| 3239 | |||
| 3240 | this.IndexElement(row, xBillboard); | ||
| 3241 | billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row); | ||
| 3242 | } | ||
| 3243 | |||
| 3244 | var billboardActions = new Dictionary<string, XElement>(); | ||
| 3245 | |||
| 3246 | foreach (var row in billboards.Values) | ||
| 3247 | { | ||
| 3248 | var xBillboard = this.GetIndexedElement(row); | ||
| 3249 | |||
| 3250 | if (!billboardActions.TryGetValue(row.FieldAsString(2), out var xBillboardAction)) | ||
| 3251 | { | ||
| 3252 | xBillboardAction = new XElement(Names.BillboardActionElement, | ||
| 3253 | new XAttribute("Id", row.FieldAsString(2))); | ||
| 3254 | |||
| 3255 | this.UIElement.Add(xBillboardAction); | ||
| 3256 | billboardActions.Add(row.FieldAsString(2), xBillboardAction); | ||
| 3257 | } | ||
| 3258 | |||
| 3259 | xBillboardAction.Add(xBillboard); | ||
| 3260 | } | ||
| 3261 | } | ||
| 3262 | |||
| 3263 | /// <summary> | ||
| 3264 | /// Decompile the Binary table. | ||
| 3265 | /// </summary> | ||
| 3266 | /// <param name="table">The table to decompile.</param> | ||
| 3267 | private void DecompileBinaryTable(Table table) | ||
| 3268 | { | ||
| 3269 | foreach (var row in table.Rows) | ||
| 3270 | { | ||
| 3271 | var xBinary = new XElement(Names.BinaryElement, | ||
| 3272 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 3273 | new XAttribute("SourceFile", row.FieldAsString(1))); | ||
| 3274 | |||
| 3275 | this.RootElement.Add(xBinary); | ||
| 3276 | } | ||
| 3277 | } | ||
| 3278 | |||
| 3279 | /// <summary> | ||
| 3280 | /// Decompile the BindImage table. | ||
| 3281 | /// </summary> | ||
| 3282 | /// <param name="table">The table to decompile.</param> | ||
| 3283 | private void DecompileBindImageTable(Table table) | ||
| 3284 | { | ||
| 3285 | foreach (var row in table.Rows) | ||
| 3286 | { | ||
| 3287 | if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) | ||
| 3288 | { | ||
| 3289 | xFile.SetAttributeValue("BindPath", row.FieldAsString(1)); | ||
| 3290 | } | ||
| 3291 | else | ||
| 3292 | { | ||
| 3293 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); | ||
| 3294 | } | ||
| 3295 | } | ||
| 3296 | } | ||
| 3297 | |||
| 3298 | /// <summary> | ||
| 3299 | /// Decompile the Class table. | ||
| 3300 | /// </summary> | ||
| 3301 | /// <param name="table">The table to decompile.</param> | ||
| 3302 | private void DecompileClassTable(Table table) | ||
| 3303 | { | ||
| 3304 | foreach (var row in table.Rows) | ||
| 3305 | { | ||
| 3306 | var xClass = new XElement(Names.ClassElement, | ||
| 3307 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 3308 | new XAttribute("Advertise", "yes"), | ||
| 3309 | new XAttribute("Context", row.FieldAsString(1)), | ||
| 3310 | row.IsColumnNull(4) ? null : new XAttribute("Description", row.FieldAsString(4)), | ||
| 3311 | row.IsColumnNull(5) ? null : new XAttribute("AppId", row.FieldAsString(5)), | ||
| 3312 | row.IsColumnNull(7) ? null : new XAttribute("Icon", row.FieldAsString(7)), | ||
| 3313 | row.IsColumnNull(8) ? null : new XAttribute("IconIndex", row.FieldAsString(8)), | ||
| 3314 | row.IsColumnNull(9) ? null : new XAttribute("Handler", row.FieldAsString(9)), | ||
| 3315 | row.IsColumnNull(10) ? null : new XAttribute("Argument", row.FieldAsString(10))); | ||
| 3316 | |||
| 3317 | if (!row.IsColumnNull(6)) | ||
| 3318 | { | ||
| 3319 | var fileTypeMaskStrings = row.FieldAsString(6).Split(';'); | ||
| 3320 | |||
| 3321 | try | ||
| 3322 | { | ||
| 3323 | foreach (var fileTypeMaskString in fileTypeMaskStrings) | ||
| 3324 | { | ||
| 3325 | var fileTypeMaskParts = fileTypeMaskString.Split(','); | ||
| 3326 | |||
| 3327 | if (4 == fileTypeMaskParts.Length) | ||
| 3328 | { | ||
| 3329 | var xFileTypeMask = new XElement(Names.FileTypeMaskElement, | ||
| 3330 | new XAttribute("Offset", Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture)), | ||
| 3331 | new XAttribute("Mask", fileTypeMaskParts[2]), | ||
| 3332 | new XAttribute("Value", fileTypeMaskParts[3])); | ||
| 3333 | |||
| 3334 | xClass.Add(xFileTypeMask); | ||
| 3335 | } | ||
| 3336 | else | ||
| 3337 | { | ||
| 3338 | // TODO: warn | ||
| 3339 | } | ||
| 3340 | } | ||
| 3341 | } | ||
| 3342 | catch (FormatException) | ||
| 3343 | { | ||
| 3344 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); | ||
| 3345 | } | ||
| 3346 | catch (OverflowException) | ||
| 3347 | { | ||
| 3348 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); | ||
| 3349 | } | ||
| 3350 | } | ||
| 3351 | |||
| 3352 | if (!row.IsColumnNull(12)) | ||
| 3353 | { | ||
| 3354 | if (1 == row.FieldAsInteger(12)) | ||
| 3355 | { | ||
| 3356 | xClass.SetAttributeValue("RelativePath", "yes"); | ||
| 3357 | } | ||
| 3358 | else | ||
| 3359 | { | ||
| 3360 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12])); | ||
| 3361 | } | ||
| 3362 | } | ||
| 3363 | |||
| 3364 | this.AddChildToParent("Component", xClass, row, 2); | ||
| 3365 | this.IndexElement(row, xClass); | ||
| 3366 | } | ||
| 3367 | } | ||
| 3368 | |||
| 3369 | /// <summary> | ||
| 3370 | /// Decompile the ComboBox table. | ||
| 3371 | /// </summary> | ||
| 3372 | /// <param name="table">The table to decompile.</param> | ||
| 3373 | private void DecompileComboBoxTable(Table table) | ||
| 3374 | { | ||
| 3375 | // sort the combo boxes by their property and order | ||
| 3376 | var comboBoxRows = table.Rows.Select(row => row).OrderBy(row => String.Format("{0}|{1:0000000000}", row.FieldAsString(0), row.FieldAsInteger(1))); | ||
| 3377 | |||
| 3378 | XElement xComboBox = null; | ||
| 3379 | string property = null; | ||
| 3380 | foreach (var row in comboBoxRows) | ||
| 3381 | { | ||
| 3382 | if (null == xComboBox || row.FieldAsString(0) != property) | ||
| 3383 | { | ||
| 3384 | property = row.FieldAsString(0); | ||
| 3385 | |||
| 3386 | xComboBox = new XElement(Names.ComboBoxElement, | ||
| 3387 | new XAttribute("Property", property)); | ||
| 3388 | |||
| 3389 | this.UIElement.Add(xComboBox); | ||
| 3390 | } | ||
| 3391 | |||
| 3392 | var xListItem = new XElement(Names.ListItemElement, | ||
| 3393 | new XAttribute("Value", row.FieldAsString(2)), | ||
| 3394 | row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); | ||
| 3395 | xComboBox.Add(xListItem); | ||
| 3396 | } | ||
| 3397 | } | ||
| 3398 | |||
| 3399 | /// <summary> | ||
| 3400 | /// Decompile the Control table. | ||
| 3401 | /// </summary> | ||
| 3402 | /// <param name="table">The table to decompile.</param> | ||
| 3403 | private void DecompileControlTable(Table table) | ||
| 3404 | { | ||
| 3405 | foreach (ControlRow controlRow in table.Rows) | ||
| 3406 | { | ||
| 3407 | var xControl = new XElement(Names.ControlElement, | ||
| 3408 | new XAttribute("Id", controlRow.Control), | ||
| 3409 | new XAttribute("Type", controlRow.Type), | ||
| 3410 | new XAttribute("X", controlRow.X), | ||
| 3411 | new XAttribute("Y", controlRow.Y), | ||
| 3412 | new XAttribute("Width", controlRow.Width), | ||
| 3413 | new XAttribute("Height", controlRow.Height), | ||
| 3414 | new XAttribute("Text", controlRow.Text)); | ||
| 3415 | |||
| 3416 | if (!controlRow.IsColumnNull(7)) | ||
| 3417 | { | ||
| 3418 | string[] specialAttributes; | ||
| 3419 | |||
| 3420 | // sets various common attributes like Disabled, Indirect, Integer, ... | ||
| 3421 | SetControlAttributes(controlRow.Attributes, xControl); | ||
| 3422 | |||
| 3423 | switch (controlRow.Type) | ||
| 3424 | { | ||
| 3425 | case "Bitmap": | ||
| 3426 | specialAttributes = BitmapControlAttributes; | ||
| 3427 | break; | ||
| 3428 | case "CheckBox": | ||
| 3429 | specialAttributes = CheckboxControlAttributes; | ||
| 3430 | break; | ||
| 3431 | case "ComboBox": | ||
| 3432 | specialAttributes = ComboboxControlAttributes; | ||
| 3433 | break; | ||
| 3434 | case "DirectoryCombo": | ||
| 3435 | specialAttributes = VolumeControlAttributes; | ||
| 3436 | break; | ||
| 3437 | case "Edit": | ||
| 3438 | specialAttributes = EditControlAttributes; | ||
| 3439 | break; | ||
| 3440 | case "Icon": | ||
| 3441 | specialAttributes = IconControlAttributes; | ||
| 3442 | break; | ||
| 3443 | case "ListBox": | ||
| 3444 | specialAttributes = ListboxControlAttributes; | ||
| 3445 | break; | ||
| 3446 | case "ListView": | ||
| 3447 | specialAttributes = ListviewControlAttributes; | ||
| 3448 | break; | ||
| 3449 | case "MaskedEdit": | ||
| 3450 | specialAttributes = EditControlAttributes; | ||
| 3451 | break; | ||
| 3452 | case "PathEdit": | ||
| 3453 | specialAttributes = EditControlAttributes; | ||
| 3454 | break; | ||
| 3455 | case "ProgressBar": | ||
| 3456 | specialAttributes = ProgressControlAttributes; | ||
| 3457 | break; | ||
| 3458 | case "PushButton": | ||
| 3459 | specialAttributes = ButtonControlAttributes; | ||
| 3460 | break; | ||
| 3461 | case "RadioButtonGroup": | ||
| 3462 | specialAttributes = RadioControlAttributes; | ||
| 3463 | break; | ||
| 3464 | case "Text": | ||
| 3465 | specialAttributes = TextControlAttributes; | ||
| 3466 | break; | ||
| 3467 | case "VolumeCostList": | ||
| 3468 | specialAttributes = VolumeControlAttributes; | ||
| 3469 | break; | ||
| 3470 | case "VolumeSelectCombo": | ||
| 3471 | specialAttributes = VolumeControlAttributes; | ||
| 3472 | break; | ||
| 3473 | default: | ||
| 3474 | specialAttributes = null; | ||
| 3475 | break; | ||
| 3476 | } | ||
| 3477 | |||
| 3478 | if (null != specialAttributes) | ||
| 3479 | { | ||
| 3480 | var iconSizeSet = false; | ||
| 3481 | |||
| 3482 | for (var i = 16; 32 > i; i++) | ||
| 3483 | { | ||
| 3484 | if (1 == ((controlRow.Attributes >> i) & 1)) | ||
| 3485 | { | ||
| 3486 | string attribute = null; | ||
| 3487 | |||
| 3488 | if (specialAttributes.Length > (i - 16)) | ||
| 3489 | { | ||
| 3490 | attribute = specialAttributes[i - 16]; | ||
| 3491 | } | ||
| 3492 | |||
| 3493 | // unknown attribute | ||
| 3494 | if (null == attribute) | ||
| 3495 | { | ||
| 3496 | this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); | ||
| 3497 | continue; | ||
| 3498 | } | ||
| 3499 | |||
| 3500 | switch (attribute) | ||
| 3501 | { | ||
| 3502 | case "Bitmap": | ||
| 3503 | xControl.SetAttributeValue("Bitmap", "yes"); | ||
| 3504 | break; | ||
| 3505 | case "CDROM": | ||
| 3506 | xControl.SetAttributeValue("CDROM", "yes"); | ||
| 3507 | break; | ||
| 3508 | case "ComboList": | ||
| 3509 | xControl.SetAttributeValue("ComboList", "yes"); | ||
| 3510 | break; | ||
| 3511 | case "ElevationShield": | ||
| 3512 | xControl.SetAttributeValue("ElevationShield", "yes"); | ||
| 3513 | break; | ||
| 3514 | case "Fixed": | ||
| 3515 | xControl.SetAttributeValue("Fixed", "yes"); | ||
| 3516 | break; | ||
| 3517 | case "FixedSize": | ||
| 3518 | xControl.SetAttributeValue("FixedSize", "yes"); | ||
| 3519 | break; | ||
| 3520 | case "Floppy": | ||
| 3521 | xControl.SetAttributeValue("Floppy", "yes"); | ||
| 3522 | break; | ||
| 3523 | case "FormatSize": | ||
| 3524 | xControl.SetAttributeValue("FormatSize", "yes"); | ||
| 3525 | break; | ||
| 3526 | case "HasBorder": | ||
| 3527 | xControl.SetAttributeValue("HasBorder", "yes"); | ||
| 3528 | break; | ||
| 3529 | case "Icon": | ||
| 3530 | xControl.SetAttributeValue("Icon", "yes"); | ||
| 3531 | break; | ||
| 3532 | case "Icon16": | ||
| 3533 | if (iconSizeSet) | ||
| 3534 | { | ||
| 3535 | xControl.SetAttributeValue("IconSize", "48"); | ||
| 3536 | } | ||
| 3537 | else | ||
| 3538 | { | ||
| 3539 | iconSizeSet = true; | ||
| 3540 | xControl.SetAttributeValue("IconSize", "16"); | ||
| 3541 | } | ||
| 3542 | break; | ||
| 3543 | case "Icon32": | ||
| 3544 | if (iconSizeSet) | ||
| 3545 | { | ||
| 3546 | xControl.SetAttributeValue("IconSize", "48"); | ||
| 3547 | } | ||
| 3548 | else | ||
| 3549 | { | ||
| 3550 | iconSizeSet = true; | ||
| 3551 | xControl.SetAttributeValue("IconSize", "32"); | ||
| 3552 | } | ||
| 3553 | break; | ||
| 3554 | case "Image": | ||
| 3555 | xControl.SetAttributeValue("Image", "yes"); | ||
| 3556 | break; | ||
| 3557 | case "Multiline": | ||
| 3558 | xControl.SetAttributeValue("Multiline", "yes"); | ||
| 3559 | break; | ||
| 3560 | case "NoPrefix": | ||
| 3561 | xControl.SetAttributeValue("NoPrefix", "yes"); | ||
| 3562 | break; | ||
| 3563 | case "NoWrap": | ||
| 3564 | xControl.SetAttributeValue("NoWrap", "yes"); | ||
| 3565 | break; | ||
| 3566 | case "Password": | ||
| 3567 | xControl.SetAttributeValue("Password", "yes"); | ||
| 3568 | break; | ||
| 3569 | case "ProgressBlocks": | ||
| 3570 | xControl.SetAttributeValue("ProgressBlocks", "yes"); | ||
| 3571 | break; | ||
| 3572 | case "PushLike": | ||
| 3573 | xControl.SetAttributeValue("PushLike", "yes"); | ||
| 3574 | break; | ||
| 3575 | case "RAMDisk": | ||
| 3576 | xControl.SetAttributeValue("RAMDisk", "yes"); | ||
| 3577 | break; | ||
| 3578 | case "Remote": | ||
| 3579 | xControl.SetAttributeValue("Remote", "yes"); | ||
| 3580 | break; | ||
| 3581 | case "Removable": | ||
| 3582 | xControl.SetAttributeValue("Removable", "yes"); | ||
| 3583 | break; | ||
| 3584 | case "ShowRollbackCost": | ||
| 3585 | xControl.SetAttributeValue("ShowRollbackCost", "yes"); | ||
| 3586 | break; | ||
| 3587 | case "Sorted": | ||
| 3588 | xControl.SetAttributeValue("Sorted", "yes"); | ||
| 3589 | break; | ||
| 3590 | case "Transparent": | ||
| 3591 | xControl.SetAttributeValue("Transparent", "yes"); | ||
| 3592 | break; | ||
| 3593 | case "UserLanguage": | ||
| 3594 | xControl.SetAttributeValue("UserLanguage", "yes"); | ||
| 3595 | break; | ||
| 3596 | default: | ||
| 3597 | throw new InvalidOperationException($"Unknown control attribute: '{attribute}'."); | ||
| 3598 | } | ||
| 3599 | } | ||
| 3600 | } | ||
| 3601 | } | ||
| 3602 | else if (0 < (controlRow.Attributes & 0xFFFF0000)) | ||
| 3603 | { | ||
| 3604 | this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); | ||
| 3605 | } | ||
| 3606 | } | ||
| 3607 | |||
| 3608 | // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef | ||
| 3609 | if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", controlRow.Type)) | ||
| 3610 | { | ||
| 3611 | xControl.SetAttributeValue("Property", controlRow.Property); | ||
| 3612 | } | ||
| 3613 | |||
| 3614 | if (null != controlRow.Help) | ||
| 3615 | { | ||
| 3616 | var help = controlRow.Help.Split('|'); | ||
| 3617 | |||
| 3618 | if (2 == help.Length) | ||
| 3619 | { | ||
| 3620 | if (0 < help[0].Length) | ||
| 3621 | { | ||
| 3622 | xControl.SetAttributeValue("ToolTip", help[0]); | ||
| 3623 | } | ||
| 3624 | |||
| 3625 | if (0 < help[1].Length) | ||
| 3626 | { | ||
| 3627 | xControl.SetAttributeValue("Help", help[1]); | ||
| 3628 | } | ||
| 3629 | } | ||
| 3630 | } | ||
| 3631 | |||
| 3632 | this.IndexElement(controlRow, xControl); | ||
| 3633 | } | ||
| 3634 | } | ||
| 3635 | |||
| 3636 | /// <summary> | ||
| 3637 | /// Decompile the ControlCondition table. | ||
| 3638 | /// </summary> | ||
| 3639 | /// <param name="table">The table to decompile.</param> | ||
| 3640 | private void DecompileControlConditionTable(Table table) | ||
| 3641 | { | ||
| 3642 | foreach (var row in table.Rows) | ||
| 3643 | { | ||
| 3644 | if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) | ||
| 3645 | { | ||
| 3646 | switch (row.FieldAsString(2)) | ||
| 3647 | { | ||
| 3648 | case "Default": | ||
| 3649 | xControl.SetAttributeValue("DefaultCondition", row.FieldAsString(3)); | ||
| 3650 | break; | ||
| 3651 | case "Disable": | ||
| 3652 | xControl.SetAttributeValue("DisableCondition", row.FieldAsString(3)); | ||
| 3653 | break; | ||
| 3654 | case "Enable": | ||
| 3655 | xControl.SetAttributeValue("EnableCondition", row.FieldAsString(3)); | ||
| 3656 | break; | ||
| 3657 | case "Hide": | ||
| 3658 | xControl.SetAttributeValue("HideCondition", row.FieldAsString(3)); | ||
| 3659 | break; | ||
| 3660 | case "Show": | ||
| 3661 | xControl.SetAttributeValue("ShowCondition", row.FieldAsString(3)); | ||
| 3662 | break; | ||
| 3663 | default: | ||
| 3664 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); | ||
| 3665 | break; | ||
| 3666 | } | ||
| 3667 | } | ||
| 3668 | else | ||
| 3669 | { | ||
| 3670 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); | ||
| 3671 | } | ||
| 3672 | } | ||
| 3673 | } | ||
| 3674 | |||
| 3675 | /// <summary> | ||
| 3676 | /// Decompile the ControlEvent table. | ||
| 3677 | /// </summary> | ||
| 3678 | /// <param name="table">The table to decompile.</param> | ||
| 3679 | private void DecompileControlEventTable(Table table) | ||
| 3680 | { | ||
| 3681 | var controlEvents = new SortedList<string, Row>(); | ||
| 3682 | |||
| 3683 | foreach (var row in table.Rows) | ||
| 3684 | { | ||
| 3685 | var xPublish = new XElement(Names.PublishElement, | ||
| 3686 | new XAttribute("Condition", row.FieldAsString(4))); | ||
| 3687 | |||
| 3688 | var publishEvent = row.FieldAsString(2); | ||
| 3689 | if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal)) | ||
| 3690 | { | ||
| 3691 | xPublish.SetAttributeValue("Property", publishEvent.Substring(1, publishEvent.Length - 2)); | ||
| 3692 | |||
| 3693 | if ("{}" != row.FieldAsString(3)) | ||
| 3694 | { | ||
| 3695 | xPublish.SetAttributeValue("Value", row.FieldAsString(3)); | ||
| 3696 | } | ||
| 3697 | } | ||
| 3698 | else | ||
| 3699 | { | ||
| 3700 | xPublish.SetAttributeValue("Event", publishEvent); | ||
| 3701 | xPublish.SetAttributeValue("Value", row.FieldAsString(3)); | ||
| 3702 | } | ||
| 3703 | |||
| 3704 | controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row.FieldAsString(0), row.FieldAsString(1), row.FieldAsNullableInteger(5) ?? 0, row.FieldAsString(2), row.FieldAsString(3), row.FieldAsString(4)), row); | ||
| 3705 | |||
| 3706 | this.IndexElement(row, xPublish); | ||
| 3707 | } | ||
| 3708 | |||
| 3709 | foreach (var row in controlEvents.Values) | ||
| 3710 | { | ||
| 3711 | if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) | ||
| 3712 | { | ||
| 3713 | var xPublish = this.GetIndexedElement(row); | ||
| 3714 | xControl.Add(xPublish); | ||
| 3715 | } | ||
| 3716 | else | ||
| 3717 | { | ||
| 3718 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); | ||
| 3719 | } | ||
| 3720 | } | ||
| 3721 | } | ||
| 3722 | |||
| 3723 | /// <summary> | ||
| 3724 | /// Decompile a custom table. | ||
| 3725 | /// </summary> | ||
| 3726 | /// <param name="table">The table to decompile.</param> | ||
| 3727 | private void DecompileCustomTable(Table table) | ||
| 3728 | { | ||
| 3729 | if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables) | ||
| 3730 | { | ||
| 3731 | this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name)); | ||
| 3732 | |||
| 3733 | var xCustomTable = new XElement(Names.CustomTableElement, | ||
| 3734 | new XAttribute("Id", table.Name)); | ||
| 3735 | |||
| 3736 | foreach (var columnDefinition in table.Definition.Columns) | ||
| 3737 | { | ||
| 3738 | var xColumn = new XElement(Names.ColumnElement, | ||
| 3739 | new XAttribute("Id", columnDefinition.Name), | ||
| 3740 | columnDefinition.Description == null ? null : new XAttribute("Description", columnDefinition.Description), | ||
| 3741 | columnDefinition.KeyTable == null ? null : new XAttribute("KeyTable", columnDefinition.KeyTable), | ||
| 3742 | !columnDefinition.KeyColumn.HasValue ? null : new XAttribute("KeyColumn", columnDefinition.KeyColumn.Value), | ||
| 3743 | !columnDefinition.IsLocalizable ? null : new XAttribute("Localizable", "yes"), | ||
| 3744 | !columnDefinition.MaxValue.HasValue ? null : new XAttribute("MaxValue", columnDefinition.MaxValue.Value), | ||
| 3745 | !columnDefinition.MinValue.HasValue ? null : new XAttribute("MinValue", columnDefinition.MinValue.Value), | ||
| 3746 | !columnDefinition.Nullable ? null : new XAttribute("Nullable", "yes"), | ||
| 3747 | !columnDefinition.PrimaryKey ? null : new XAttribute("PrimaryKey", "yes"), | ||
| 3748 | columnDefinition.Possibilities == null ? null : new XAttribute("Possibilities", "yes"), | ||
| 3749 | new XAttribute("Width", columnDefinition.Length)); | ||
| 3750 | |||
| 3751 | if (ColumnCategory.Unknown != columnDefinition.Category) | ||
| 3752 | { | ||
| 3753 | switch (columnDefinition.Category) | ||
| 3754 | { | ||
| 3755 | case ColumnCategory.Text: | ||
| 3756 | xColumn.SetAttributeValue("Category", "text"); | ||
| 3757 | break; | ||
| 3758 | case ColumnCategory.UpperCase: | ||
| 3759 | xColumn.SetAttributeValue("Category", "upperCase"); | ||
| 3760 | break; | ||
| 3761 | case ColumnCategory.LowerCase: | ||
| 3762 | xColumn.SetAttributeValue("Category", "lowerCase"); | ||
| 3763 | break; | ||
| 3764 | case ColumnCategory.Integer: | ||
| 3765 | xColumn.SetAttributeValue("Category", "integer"); | ||
| 3766 | break; | ||
| 3767 | case ColumnCategory.DoubleInteger: | ||
| 3768 | xColumn.SetAttributeValue("Category", "doubleInteger"); | ||
| 3769 | break; | ||
| 3770 | case ColumnCategory.TimeDate: | ||
| 3771 | xColumn.SetAttributeValue("Category", "timeDate"); | ||
| 3772 | break; | ||
| 3773 | case ColumnCategory.Identifier: | ||
| 3774 | xColumn.SetAttributeValue("Category", "identifier"); | ||
| 3775 | break; | ||
| 3776 | case ColumnCategory.Property: | ||
| 3777 | xColumn.SetAttributeValue("Category", "property"); | ||
| 3778 | break; | ||
| 3779 | case ColumnCategory.Filename: | ||
| 3780 | xColumn.SetAttributeValue("Category", "filename"); | ||
| 3781 | break; | ||
| 3782 | case ColumnCategory.WildCardFilename: | ||
| 3783 | xColumn.SetAttributeValue("Category", "wildCardFilename"); | ||
| 3784 | break; | ||
| 3785 | case ColumnCategory.Path: | ||
| 3786 | xColumn.SetAttributeValue("Category", "path"); | ||
| 3787 | break; | ||
| 3788 | case ColumnCategory.Paths: | ||
| 3789 | xColumn.SetAttributeValue("Category", "paths"); | ||
| 3790 | break; | ||
| 3791 | case ColumnCategory.AnyPath: | ||
| 3792 | xColumn.SetAttributeValue("Category", "anyPath"); | ||
| 3793 | break; | ||
| 3794 | case ColumnCategory.DefaultDir: | ||
| 3795 | xColumn.SetAttributeValue("Category", "defaultDir"); | ||
| 3796 | break; | ||
| 3797 | case ColumnCategory.RegPath: | ||
| 3798 | xColumn.SetAttributeValue("Category", "regPath"); | ||
| 3799 | break; | ||
| 3800 | case ColumnCategory.Formatted: | ||
| 3801 | xColumn.SetAttributeValue("Category", "formatted"); | ||
| 3802 | break; | ||
| 3803 | case ColumnCategory.FormattedSDDLText: | ||
| 3804 | xColumn.SetAttributeValue("Category", "formattedSddl"); | ||
| 3805 | break; | ||
| 3806 | case ColumnCategory.Template: | ||
| 3807 | xColumn.SetAttributeValue("Category", "template"); | ||
| 3808 | break; | ||
| 3809 | case ColumnCategory.Condition: | ||
| 3810 | xColumn.SetAttributeValue("Category", "condition"); | ||
| 3811 | break; | ||
| 3812 | case ColumnCategory.Guid: | ||
| 3813 | xColumn.SetAttributeValue("Category", "guid"); | ||
| 3814 | break; | ||
| 3815 | case ColumnCategory.Version: | ||
| 3816 | xColumn.SetAttributeValue("Category", "version"); | ||
| 3817 | break; | ||
| 3818 | case ColumnCategory.Language: | ||
| 3819 | xColumn.SetAttributeValue("Category", "language"); | ||
| 3820 | break; | ||
| 3821 | case ColumnCategory.Binary: | ||
| 3822 | xColumn.SetAttributeValue("Category", "binary"); | ||
| 3823 | break; | ||
| 3824 | case ColumnCategory.CustomSource: | ||
| 3825 | xColumn.SetAttributeValue("Category", "customSource"); | ||
| 3826 | break; | ||
| 3827 | case ColumnCategory.Cabinet: | ||
| 3828 | xColumn.SetAttributeValue("Category", "cabinet"); | ||
| 3829 | break; | ||
| 3830 | case ColumnCategory.Shortcut: | ||
| 3831 | xColumn.SetAttributeValue("Category", "shortcut"); | ||
| 3832 | break; | ||
| 3833 | default: | ||
| 3834 | throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'."); | ||
| 3835 | } | ||
| 3836 | } | ||
| 3837 | |||
| 3838 | if (ColumnModularizeType.None != columnDefinition.ModularizeType) | ||
| 3839 | { | ||
| 3840 | switch (columnDefinition.ModularizeType) | ||
| 3841 | { | ||
| 3842 | case ColumnModularizeType.Column: | ||
| 3843 | xColumn.SetAttributeValue("Modularize", "Column"); | ||
| 3844 | break; | ||
| 3845 | case ColumnModularizeType.Condition: | ||
| 3846 | xColumn.SetAttributeValue("Modularize", "Condition"); | ||
| 3847 | break; | ||
| 3848 | case ColumnModularizeType.Icon: | ||
| 3849 | xColumn.SetAttributeValue("Modularize", "Icon"); | ||
| 3850 | break; | ||
| 3851 | case ColumnModularizeType.Property: | ||
| 3852 | xColumn.SetAttributeValue("Modularize", "Property"); | ||
| 3853 | break; | ||
| 3854 | case ColumnModularizeType.SemicolonDelimited: | ||
| 3855 | xColumn.SetAttributeValue("Modularize", "SemicolonDelimited"); | ||
| 3856 | break; | ||
| 3857 | default: | ||
| 3858 | throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'."); | ||
| 3859 | } | ||
| 3860 | } | ||
| 3861 | |||
| 3862 | if (ColumnType.Unknown != columnDefinition.Type) | ||
| 3863 | { | ||
| 3864 | switch (columnDefinition.Type) | ||
| 3865 | { | ||
| 3866 | case ColumnType.Localized: | ||
| 3867 | xColumn.SetAttributeValue("Localizable", "yes"); | ||
| 3868 | xColumn.SetAttributeValue("Type", "string"); | ||
| 3869 | break; | ||
| 3870 | case ColumnType.Number: | ||
| 3871 | xColumn.SetAttributeValue("Type", "int"); | ||
| 3872 | break; | ||
| 3873 | case ColumnType.Object: | ||
| 3874 | xColumn.SetAttributeValue("Type", "binary"); | ||
| 3875 | break; | ||
| 3876 | case ColumnType.Preserved: | ||
| 3877 | case ColumnType.String: | ||
| 3878 | xColumn.SetAttributeValue("Type", "string"); | ||
| 3879 | break; | ||
| 3880 | default: | ||
| 3881 | throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type}'."); | ||
| 3882 | } | ||
| 3883 | } | ||
| 3884 | |||
| 3885 | xCustomTable.Add(xColumn); | ||
| 3886 | } | ||
| 3887 | |||
| 3888 | foreach (var row in table.Rows) | ||
| 3889 | { | ||
| 3890 | var xRow = new XElement(Names.RowElement); | ||
| 3891 | |||
| 3892 | foreach (var field in row.Fields.Where(f => f.Data != null)) | ||
| 3893 | { | ||
| 3894 | var xData = new XElement(Names.DataElement, | ||
| 3895 | new XAttribute("Column", field.Column.Name), | ||
| 3896 | new XAttribute("Value", field.AsString())); | ||
| 3897 | |||
| 3898 | xRow.Add(xData); | ||
| 3899 | } | ||
| 3900 | |||
| 3901 | xCustomTable.Add(xRow); | ||
| 3902 | } | ||
| 3903 | |||
| 3904 | this.RootElement.Add(xCustomTable); | ||
| 3905 | } | ||
| 3906 | } | ||
| 3907 | |||
| 3908 | /// <summary> | ||
| 3909 | /// Decompile the CreateFolder table. | ||
| 3910 | /// </summary> | ||
| 3911 | /// <param name="table">The table to decompile.</param> | ||
| 3912 | private void DecompileCreateFolderTable(Table table) | ||
| 3913 | { | ||
| 3914 | foreach (var row in table.Rows) | ||
| 3915 | { | ||
| 3916 | var xCreateFolder = new XElement(Names.CreateFolderElement, | ||
| 3917 | new XAttribute("Directory", row.FieldAsString(0))); | ||
| 3918 | |||
| 3919 | this.AddChildToParent("Component", xCreateFolder, row, 1); | ||
| 3920 | this.IndexElement(row, xCreateFolder); | ||
| 3921 | } | ||
| 3922 | } | ||
| 3923 | |||
| 3924 | /// <summary> | ||
| 3925 | /// Decompile the CustomAction table. | ||
| 3926 | /// </summary> | ||
| 3927 | /// <param name="table">The table to decompile.</param> | ||
| 3928 | private void DecompileCustomActionTable(Table table) | ||
| 3929 | { | ||
| 3930 | foreach (var row in table.Rows) | ||
| 3931 | { | ||
| 3932 | var xCustomAction = new XElement(Names.CustomActionElement, | ||
| 3933 | new XAttribute("Id", row.FieldAsString(0))); | ||
| 3934 | |||
| 3935 | var type = row.FieldAsInteger(1); | ||
| 3936 | |||
| 3937 | if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (type & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget)) | ||
| 3938 | { | ||
| 3939 | xCustomAction.SetAttributeValue("HideTarget", "yes"); | ||
| 3940 | } | ||
| 3941 | |||
| 3942 | if (WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate == (type & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate)) | ||
| 3943 | { | ||
| 3944 | xCustomAction.SetAttributeValue("Impersonate", "no"); | ||
| 3945 | } | ||
| 3946 | |||
| 3947 | if (WindowsInstallerConstants.MsidbCustomActionTypeTSAware == (type & WindowsInstallerConstants.MsidbCustomActionTypeTSAware)) | ||
| 3948 | { | ||
| 3949 | xCustomAction.SetAttributeValue("TerminalServerAware", "yes"); | ||
| 3950 | } | ||
| 3951 | |||
| 3952 | if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript)) | ||
| 3953 | { | ||
| 3954 | xCustomAction.SetAttributeValue("Bitness", "always64"); | ||
| 3955 | } | ||
| 3956 | else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) || | ||
| 3957 | WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript)) | ||
| 3958 | { | ||
| 3959 | xCustomAction.SetAttributeValue("Bitness", "always32"); | ||
| 3960 | } | ||
| 3961 | |||
| 3962 | switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits) | ||
| 3963 | { | ||
| 3964 | case 0: | ||
| 3965 | // this is the default value | ||
| 3966 | break; | ||
| 3967 | case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence: | ||
| 3968 | xCustomAction.SetAttributeValue("Execute", "firstSequence"); | ||
| 3969 | break; | ||
| 3970 | case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess: | ||
| 3971 | xCustomAction.SetAttributeValue("Execute", "oncePerProcess"); | ||
| 3972 | break; | ||
| 3973 | case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat: | ||
| 3974 | xCustomAction.SetAttributeValue("Execute", "secondSequence"); | ||
| 3975 | break; | ||
| 3976 | case WindowsInstallerConstants.MsidbCustomActionTypeInScript: | ||
| 3977 | xCustomAction.SetAttributeValue("Execute", "deferred"); | ||
| 3978 | break; | ||
| 3979 | case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback: | ||
| 3980 | xCustomAction.SetAttributeValue("Execute", "rollback"); | ||
| 3981 | break; | ||
| 3982 | case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit: | ||
| 3983 | xCustomAction.SetAttributeValue("Execute", "commit"); | ||
| 3984 | break; | ||
| 3985 | default: | ||
| 3986 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 3987 | break; | ||
| 3988 | } | ||
| 3989 | |||
| 3990 | switch (type & WindowsInstallerConstants.MsidbCustomActionTypeReturnBits) | ||
| 3991 | { | ||
| 3992 | case 0: | ||
| 3993 | // this is the default value | ||
| 3994 | break; | ||
| 3995 | case WindowsInstallerConstants.MsidbCustomActionTypeContinue: | ||
| 3996 | xCustomAction.SetAttributeValue("Return", "ignore"); | ||
| 3997 | break; | ||
| 3998 | case WindowsInstallerConstants.MsidbCustomActionTypeAsync: | ||
| 3999 | xCustomAction.SetAttributeValue("Return", "asyncWait"); | ||
| 4000 | break; | ||
| 4001 | case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue: | ||
| 4002 | xCustomAction.SetAttributeValue("Return", "asyncNoWait"); | ||
| 4003 | break; | ||
| 4004 | default: | ||
| 4005 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 4006 | break; | ||
| 4007 | } | ||
| 4008 | |||
| 4009 | var source = type & WindowsInstallerConstants.MsidbCustomActionTypeSourceBits; | ||
| 4010 | switch (source) | ||
| 4011 | { | ||
| 4012 | case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: | ||
| 4013 | xCustomAction.SetAttributeValue("BinaryRef", row.FieldAsString(2)); | ||
| 4014 | break; | ||
| 4015 | case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: | ||
| 4016 | if (!row.IsColumnNull(2)) | ||
| 4017 | { | ||
| 4018 | xCustomAction.SetAttributeValue("FileRef", row.FieldAsString(2)); | ||
| 4019 | } | ||
| 4020 | break; | ||
| 4021 | case WindowsInstallerConstants.MsidbCustomActionTypeDirectory: | ||
| 4022 | if (!row.IsColumnNull(2)) | ||
| 4023 | { | ||
| 4024 | xCustomAction.SetAttributeValue("Directory", row.FieldAsString(2)); | ||
| 4025 | } | ||
| 4026 | break; | ||
| 4027 | case WindowsInstallerConstants.MsidbCustomActionTypeProperty: | ||
| 4028 | xCustomAction.SetAttributeValue("Property", row.FieldAsString(2)); | ||
| 4029 | break; | ||
| 4030 | default: | ||
| 4031 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 4032 | break; | ||
| 4033 | } | ||
| 4034 | |||
| 4035 | switch (type & WindowsInstallerConstants.MsidbCustomActionTypeTargetBits) | ||
| 4036 | { | ||
| 4037 | case WindowsInstallerConstants.MsidbCustomActionTypeDll: | ||
| 4038 | xCustomAction.SetAttributeValue("DllEntry", row.FieldAsString(3)); | ||
| 4039 | break; | ||
| 4040 | case WindowsInstallerConstants.MsidbCustomActionTypeExe: | ||
| 4041 | xCustomAction.SetAttributeValue("ExeCommand", row.FieldAsString(3)); | ||
| 4042 | break; | ||
| 4043 | case WindowsInstallerConstants.MsidbCustomActionTypeTextData: | ||
| 4044 | if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source) | ||
| 4045 | { | ||
| 4046 | xCustomAction.SetAttributeValue("Error", row.FieldAsString(3)); | ||
| 4047 | } | ||
| 4048 | else | ||
| 4049 | { | ||
| 4050 | xCustomAction.SetAttributeValue("Value", row.FieldAsString(3)); | ||
| 4051 | } | ||
| 4052 | break; | ||
| 4053 | case WindowsInstallerConstants.MsidbCustomActionTypeJScript: | ||
| 4054 | if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) | ||
| 4055 | { | ||
| 4056 | xCustomAction.SetAttributeValue("Script", "jscript"); | ||
| 4057 | // TODO: Extract to @ScriptFile? | ||
| 4058 | // xCustomAction.Content = row.FieldAsString(3); | ||
| 4059 | } | ||
| 4060 | else | ||
| 4061 | { | ||
| 4062 | xCustomAction.SetAttributeValue("JScriptCall", row.FieldAsString(3)); | ||
| 4063 | } | ||
| 4064 | break; | ||
| 4065 | case WindowsInstallerConstants.MsidbCustomActionTypeVBScript: | ||
| 4066 | if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) | ||
| 4067 | { | ||
| 4068 | xCustomAction.SetAttributeValue("Script", "vbscript"); | ||
| 4069 | // TODO: Extract to @ScriptFile? | ||
| 4070 | // xCustomAction.Content = row.FieldAsString(3); | ||
| 4071 | } | ||
| 4072 | else | ||
| 4073 | { | ||
| 4074 | xCustomAction.SetAttributeValue("VBScriptCall", row.FieldAsString(3)); | ||
| 4075 | } | ||
| 4076 | break; | ||
| 4077 | case WindowsInstallerConstants.MsidbCustomActionTypeInstall: | ||
| 4078 | this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 4079 | continue; | ||
| 4080 | default: | ||
| 4081 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 4082 | break; | ||
| 4083 | } | ||
| 4084 | |||
| 4085 | var extype = 4 < row.Fields.Length && !row.IsColumnNull(4) ? row.FieldAsInteger(4) : 0; | ||
| 4086 | if (WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall == (extype & WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall)) | ||
| 4087 | { | ||
| 4088 | xCustomAction.SetAttributeValue("PatchUninstall", "yes"); | ||
| 4089 | } | ||
| 4090 | |||
| 4091 | this.RootElement.Add(xCustomAction); | ||
| 4092 | this.IndexElement(row, xCustomAction); | ||
| 4093 | } | ||
| 4094 | } | ||
| 4095 | |||
| 4096 | /// <summary> | ||
| 4097 | /// Decompile the CompLocator table. | ||
| 4098 | /// </summary> | ||
| 4099 | /// <param name="table">The table to decompile.</param> | ||
| 4100 | private void DecompileCompLocatorTable(Table table) | ||
| 4101 | { | ||
| 4102 | foreach (var row in table.Rows) | ||
| 4103 | { | ||
| 4104 | var xComponentSearch = new XElement(Names.ComponentSearchElement, | ||
| 4105 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4106 | new XAttribute("Guid", row.FieldAsString(1))); | ||
| 4107 | |||
| 4108 | if (!row.IsColumnNull(2)) | ||
| 4109 | { | ||
| 4110 | switch (row.FieldAsInteger(2)) | ||
| 4111 | { | ||
| 4112 | case WindowsInstallerConstants.MsidbLocatorTypeDirectory: | ||
| 4113 | xComponentSearch.SetAttributeValue("Type", "directory"); | ||
| 4114 | break; | ||
| 4115 | case WindowsInstallerConstants.MsidbLocatorTypeFileName: | ||
| 4116 | // this is the default value | ||
| 4117 | break; | ||
| 4118 | default: | ||
| 4119 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); | ||
| 4120 | break; | ||
| 4121 | } | ||
| 4122 | } | ||
| 4123 | |||
| 4124 | this.IndexElement(row, xComponentSearch); | ||
| 4125 | } | ||
| 4126 | } | ||
| 4127 | |||
| 4128 | /// <summary> | ||
| 4129 | /// Decompile the Complus table. | ||
| 4130 | /// </summary> | ||
| 4131 | /// <param name="table">The table to decompile.</param> | ||
| 4132 | private void DecompileComplusTable(Table table) | ||
| 4133 | { | ||
| 4134 | foreach (var row in table.Rows) | ||
| 4135 | { | ||
| 4136 | if (!row.IsColumnNull(1)) | ||
| 4137 | { | ||
| 4138 | if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) | ||
| 4139 | { | ||
| 4140 | xComponent.SetAttributeValue("ComPlusFlags", row.FieldAsInteger(1)); | ||
| 4141 | } | ||
| 4142 | else | ||
| 4143 | { | ||
| 4144 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); | ||
| 4145 | } | ||
| 4146 | } | ||
| 4147 | } | ||
| 4148 | } | ||
| 4149 | |||
| 4150 | /// <summary> | ||
| 4151 | /// Decompile the Component table. | ||
| 4152 | /// </summary> | ||
| 4153 | /// <param name="table">The table to decompile.</param> | ||
| 4154 | private void DecompileComponentTable(Table table) | ||
| 4155 | { | ||
| 4156 | foreach (var row in table.Rows) | ||
| 4157 | { | ||
| 4158 | var xComponent = new XElement(Names.ComponentElement, | ||
| 4159 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4160 | new XAttribute("Guid", row.FieldAsString(1) ?? String.Empty)); | ||
| 4161 | |||
| 4162 | var attributes = row.FieldAsInteger(3); | ||
| 4163 | |||
| 4164 | if (WindowsInstallerConstants.MsidbComponentAttributesSourceOnly == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly)) | ||
| 4165 | { | ||
| 4166 | xComponent.SetAttributeValue("Location", "source"); | ||
| 4167 | } | ||
| 4168 | else if (WindowsInstallerConstants.MsidbComponentAttributesOptional == (attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional)) | ||
| 4169 | { | ||
| 4170 | xComponent.SetAttributeValue("Location", "either"); | ||
| 4171 | } | ||
| 4172 | |||
| 4173 | if (WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount)) | ||
| 4174 | { | ||
| 4175 | xComponent.SetAttributeValue("SharedDllRefCount", "yes"); | ||
| 4176 | } | ||
| 4177 | |||
| 4178 | if (WindowsInstallerConstants.MsidbComponentAttributesPermanent == (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent)) | ||
| 4179 | { | ||
| 4180 | xComponent.SetAttributeValue("Permanent", "yes"); | ||
| 4181 | } | ||
| 4182 | |||
| 4183 | if (WindowsInstallerConstants.MsidbComponentAttributesTransitive == (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive)) | ||
| 4184 | { | ||
| 4185 | xComponent.SetAttributeValue("Transitive", "yes"); | ||
| 4186 | } | ||
| 4187 | |||
| 4188 | if (WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite == (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite)) | ||
| 4189 | { | ||
| 4190 | xComponent.SetAttributeValue("NeverOverwrite", "yes"); | ||
| 4191 | } | ||
| 4192 | |||
| 4193 | if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit)) | ||
| 4194 | { | ||
| 4195 | xComponent.SetAttributeValue("Bitness", "always64"); | ||
| 4196 | } | ||
| 4197 | else | ||
| 4198 | { | ||
| 4199 | xComponent.SetAttributeValue("Bitness", "always32"); | ||
| 4200 | } | ||
| 4201 | |||
| 4202 | if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection)) | ||
| 4203 | { | ||
| 4204 | xComponent.SetAttributeValue("DisableRegistryReflection", "yes"); | ||
| 4205 | } | ||
| 4206 | |||
| 4207 | if (WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence == (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence)) | ||
| 4208 | { | ||
| 4209 | xComponent.SetAttributeValue("UninstallWhenSuperseded", "yes"); | ||
| 4210 | } | ||
| 4211 | |||
| 4212 | if (WindowsInstallerConstants.MsidbComponentAttributesShared == (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared)) | ||
| 4213 | { | ||
| 4214 | xComponent.SetAttributeValue("Shared", "yes"); | ||
| 4215 | } | ||
| 4216 | |||
| 4217 | if (!row.IsColumnNull(4)) | ||
| 4218 | { | ||
| 4219 | xComponent.SetAttributeValue("Condition", row.FieldAsString(4)); | ||
| 4220 | } | ||
| 4221 | |||
| 4222 | this.AddChildToParent("Directory", xComponent, row, 2); | ||
| 4223 | this.IndexElement(row, xComponent); | ||
| 4224 | } | ||
| 4225 | } | ||
| 4226 | |||
| 4227 | /// <summary> | ||
| 4228 | /// Decompile the Condition table. | ||
| 4229 | /// </summary> | ||
| 4230 | /// <param name="table">The table to decompile.</param> | ||
| 4231 | private void DecompileConditionTable(Table table) | ||
| 4232 | { | ||
| 4233 | foreach (var row in table.Rows) | ||
| 4234 | { | ||
| 4235 | if (this.TryGetIndexedElement("Feature", out var xFeature, row.FieldAsString(0))) | ||
| 4236 | { | ||
| 4237 | var xLevel = new XElement(Names.LevelElement, | ||
| 4238 | row.IsColumnNull(2) ? null : new XAttribute("Condition", row.FieldAsString(2)), | ||
| 4239 | new XAttribute("Level", row.FieldAsInteger(1))); | ||
| 4240 | |||
| 4241 | xFeature.Add(xLevel); | ||
| 4242 | } | ||
| 4243 | else | ||
| 4244 | { | ||
| 4245 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", row.FieldAsString(0), "Feature")); | ||
| 4246 | } | ||
| 4247 | } | ||
| 4248 | } | ||
| 4249 | |||
| 4250 | /// <summary> | ||
| 4251 | /// Decompile the Dialog table. | ||
| 4252 | /// </summary> | ||
| 4253 | /// <param name="table">The table to decompile.</param> | ||
| 4254 | private void DecompileDialogTable(Table table) | ||
| 4255 | { | ||
| 4256 | foreach (var row in table.Rows) | ||
| 4257 | { | ||
| 4258 | var attributes = row.FieldAsNullableInteger(5) ?? 0; | ||
| 4259 | |||
| 4260 | var xDialog = new XElement(Names.DialogElement, | ||
| 4261 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4262 | new XAttribute("X", row.FieldAsString(1)), | ||
| 4263 | new XAttribute("Y", row.FieldAsString(2)), | ||
| 4264 | new XAttribute("Width", row.FieldAsString(3)), | ||
| 4265 | new XAttribute("Height", row.FieldAsString(4)), | ||
| 4266 | 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible) ? new XAttribute("Hidden", "yes") : null, | ||
| 4267 | 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal) ? new XAttribute("Modeless", "yes") : null, | ||
| 4268 | 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, | ||
| 4269 | 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, | ||
| 4270 | WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal) ? new XAttribute("SystemModal", "yes") : null, | ||
| 4271 | WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless) ? new XAttribute("KeepModeless", "yes") : null, | ||
| 4272 | WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace) ? new XAttribute("TrackDiskSpace", "yes") : null, | ||
| 4273 | WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette) ? new XAttribute("CustomPalette", "yes") : null, | ||
| 4274 | WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll) ? new XAttribute("LeftScroll", "yes") : null, | ||
| 4275 | WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError) ? new XAttribute("ErrorDialog", "yes") : null, | ||
| 4276 | !row.IsColumnNull(6) ? new XAttribute("Title", row.FieldAsString(6)) : null); | ||
| 4277 | |||
| 4278 | this.UIElement.Add(xDialog); | ||
| 4279 | this.IndexElement(row, xDialog); | ||
| 4280 | } | ||
| 4281 | } | ||
| 4282 | |||
| 4283 | /// <summary> | ||
| 4284 | /// Decompile the Directory table. | ||
| 4285 | /// </summary> | ||
| 4286 | /// <param name="table">The table to decompile.</param> | ||
| 4287 | private void DecompileDirectoryTable(Table table) | ||
| 4288 | { | ||
| 4289 | foreach (var row in table.Rows) | ||
| 4290 | { | ||
| 4291 | var id = row.FieldAsString(0); | ||
| 4292 | var elementName = WindowsInstallerStandard.IsStandardDirectory(id) ? Names.StandardDirectoryElement : Names.DirectoryElement; | ||
| 4293 | var xDirectory = new XElement(elementName, | ||
| 4294 | new XAttribute("Id", id)); | ||
| 4295 | |||
| 4296 | if (!WindowsInstallerStandard.IsStandardDirectory(id)) | ||
| 4297 | { | ||
| 4298 | var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); | ||
| 4299 | |||
| 4300 | if (id == "TARGETDIR" && names[0] != "SourceDir") | ||
| 4301 | { | ||
| 4302 | this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir()); | ||
| 4303 | xDirectory.SetAttributeValue("Name", "SourceDir"); | ||
| 4304 | } | ||
| 4305 | else | ||
| 4306 | { | ||
| 4307 | if (null != names[0] && "." != names[0]) | ||
| 4308 | { | ||
| 4309 | if (null != names[1]) | ||
| 4310 | { | ||
| 4311 | xDirectory.SetAttributeValue("ShortName", names[0]); | ||
| 4312 | } | ||
| 4313 | else | ||
| 4314 | { | ||
| 4315 | xDirectory.SetAttributeValue("Name", names[0]); | ||
| 4316 | } | ||
| 4317 | } | ||
| 4318 | |||
| 4319 | if (null != names[1]) | ||
| 4320 | { | ||
| 4321 | xDirectory.SetAttributeValue("Name", names[1]); | ||
| 4322 | } | ||
| 4323 | } | ||
| 4324 | |||
| 4325 | if (null != names[2]) | ||
| 4326 | { | ||
| 4327 | if (null != names[3]) | ||
| 4328 | { | ||
| 4329 | xDirectory.SetAttributeValue("ShortSourceName", names[2]); | ||
| 4330 | } | ||
| 4331 | else | ||
| 4332 | { | ||
| 4333 | xDirectory.SetAttributeValue("SourceName", names[2]); | ||
| 4334 | } | ||
| 4335 | } | ||
| 4336 | |||
| 4337 | if (null != names[3]) | ||
| 4338 | { | ||
| 4339 | xDirectory.SetAttributeValue("SourceName", names[3]); | ||
| 4340 | } | ||
| 4341 | } | ||
| 4342 | |||
| 4343 | this.IndexElement(row, xDirectory); | ||
| 4344 | } | ||
| 4345 | |||
| 4346 | // nest the directories | ||
| 4347 | foreach (var row in table.Rows) | ||
| 4348 | { | ||
| 4349 | var xDirectory = this.GetIndexedElement(row); | ||
| 4350 | |||
| 4351 | var id = row.FieldAsString(0); | ||
| 4352 | |||
| 4353 | if (id == "TARGETDIR") | ||
| 4354 | { | ||
| 4355 | // Skip TARGETDIR (but see below!). | ||
| 4356 | } | ||
| 4357 | else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id)) | ||
| 4358 | { | ||
| 4359 | this.RootElement.Add(xDirectory); | ||
| 4360 | } | ||
| 4361 | else | ||
| 4362 | { | ||
| 4363 | var parentDirectoryId = row.FieldAsString(1); | ||
| 4364 | |||
| 4365 | if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, parentDirectoryId)) | ||
| 4366 | { | ||
| 4367 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory")); | ||
| 4368 | } | ||
| 4369 | else if (xParentDirectory == xDirectory) // another way to specify a root directory | ||
| 4370 | { | ||
| 4371 | this.RootElement.Add(xDirectory); | ||
| 4372 | } | ||
| 4373 | else | ||
| 4374 | { | ||
| 4375 | // TARGETDIR is omitted but if this directory is a first-generation descendant, add it as a root. | ||
| 4376 | if (parentDirectoryId == "TARGETDIR") | ||
| 4377 | { | ||
| 4378 | this.RootElement.Add(xDirectory); | ||
| 4379 | } | ||
| 4380 | else | ||
| 4381 | { | ||
| 4382 | xParentDirectory.Add(xDirectory); | ||
| 4383 | } | ||
| 4384 | } | ||
| 4385 | } | ||
| 4386 | } | ||
| 4387 | } | ||
| 4388 | |||
| 4389 | /// <summary> | ||
| 4390 | /// Decompile the DrLocator table. | ||
| 4391 | /// </summary> | ||
| 4392 | /// <param name="table">The table to decompile.</param> | ||
| 4393 | private void DecompileDrLocatorTable(Table table) | ||
| 4394 | { | ||
| 4395 | foreach (var row in table.Rows) | ||
| 4396 | { | ||
| 4397 | var xDirectorySearch = new XElement(Names.DirectorySearchElement, | ||
| 4398 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4399 | XAttributeIfNotNull("Path", row, 2), | ||
| 4400 | XAttributeIfNotNull("Depth", row, 3)); | ||
| 4401 | |||
| 4402 | this.IndexElement(row, xDirectorySearch); | ||
| 4403 | } | ||
| 4404 | } | ||
| 4405 | |||
| 4406 | /// <summary> | ||
| 4407 | /// Decompile the DuplicateFile table. | ||
| 4408 | /// </summary> | ||
| 4409 | /// <param name="table">The table to decompile.</param> | ||
| 4410 | private void DecompileDuplicateFileTable(Table table) | ||
| 4411 | { | ||
| 4412 | foreach (var row in table.Rows) | ||
| 4413 | { | ||
| 4414 | var xCopyFile = new XElement(Names.CopyFileElement, | ||
| 4415 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4416 | new XAttribute("FileId", row.FieldAsString(2))); | ||
| 4417 | |||
| 4418 | if (!row.IsColumnNull(3)) | ||
| 4419 | { | ||
| 4420 | var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); | ||
| 4421 | if (null != names[0] && null != names[1]) | ||
| 4422 | { | ||
| 4423 | xCopyFile.SetAttributeValue("DestinationShortName", names[0]); | ||
| 4424 | xCopyFile.SetAttributeValue("DestinationName", names[1]); | ||
| 4425 | } | ||
| 4426 | else if (null != names[0]) | ||
| 4427 | { | ||
| 4428 | xCopyFile.SetAttributeValue("DestinationName", names[0]); | ||
| 4429 | } | ||
| 4430 | } | ||
| 4431 | |||
| 4432 | // destination directory/property is set in FinalizeDuplicateMoveFileTables | ||
| 4433 | |||
| 4434 | this.AddChildToParent("Component", xCopyFile, row, 1); | ||
| 4435 | this.IndexElement(row, xCopyFile); | ||
| 4436 | } | ||
| 4437 | } | ||
| 4438 | |||
| 4439 | /// <summary> | ||
| 4440 | /// Decompile the Environment table. | ||
| 4441 | /// </summary> | ||
| 4442 | /// <param name="table">The table to decompile.</param> | ||
| 4443 | private void DecompileEnvironmentTable(Table table) | ||
| 4444 | { | ||
| 4445 | foreach (var row in table.Rows) | ||
| 4446 | { | ||
| 4447 | var xEnvironment = new XElement(Names.EnvironmentElement, | ||
| 4448 | new XAttribute("Id", row.FieldAsString(0))); | ||
| 4449 | |||
| 4450 | var done = false; | ||
| 4451 | var permanent = true; | ||
| 4452 | var name = row.FieldAsString(1); | ||
| 4453 | for (var i = 0; i < name.Length && !done; i++) | ||
| 4454 | { | ||
| 4455 | switch (name[i]) | ||
| 4456 | { | ||
| 4457 | case '=': | ||
| 4458 | xEnvironment.SetAttributeValue("Action", "set"); | ||
| 4459 | break; | ||
| 4460 | case '+': | ||
| 4461 | xEnvironment.SetAttributeValue("Action", "create"); | ||
| 4462 | break; | ||
| 4463 | case '-': | ||
| 4464 | permanent = false; | ||
| 4465 | break; | ||
| 4466 | case '!': | ||
| 4467 | xEnvironment.SetAttributeValue("Action", "remove"); | ||
| 4468 | break; | ||
| 4469 | case '*': | ||
| 4470 | xEnvironment.SetAttributeValue("System", "yes"); | ||
| 4471 | break; | ||
| 4472 | default: | ||
| 4473 | xEnvironment.SetAttributeValue("Name", name.Substring(i)); | ||
| 4474 | done = true; | ||
| 4475 | break; | ||
| 4476 | } | ||
| 4477 | } | ||
| 4478 | |||
| 4479 | if (permanent) | ||
| 4480 | { | ||
| 4481 | xEnvironment.SetAttributeValue("Permanent", "yes"); | ||
| 4482 | } | ||
| 4483 | |||
| 4484 | if (!row.IsColumnNull(2)) | ||
| 4485 | { | ||
| 4486 | var value = row.FieldAsString(2); | ||
| 4487 | |||
| 4488 | if (value.StartsWith("[~]", StringComparison.Ordinal)) | ||
| 4489 | { | ||
| 4490 | xEnvironment.SetAttributeValue("Part", "last"); | ||
| 4491 | |||
| 4492 | if (3 < value.Length) | ||
| 4493 | { | ||
| 4494 | xEnvironment.SetAttributeValue("Separator", value.Substring(3, 1)); | ||
| 4495 | xEnvironment.SetAttributeValue("Value", value.Substring(4)); | ||
| 4496 | } | ||
| 4497 | } | ||
| 4498 | else if (value.EndsWith("[~]", StringComparison.Ordinal)) | ||
| 4499 | { | ||
| 4500 | xEnvironment.SetAttributeValue("Part", "first"); | ||
| 4501 | |||
| 4502 | if (3 < value.Length) | ||
| 4503 | { | ||
| 4504 | xEnvironment.SetAttributeValue("Separator", value.Substring(value.Length - 4, 1)); | ||
| 4505 | xEnvironment.SetAttributeValue("Value", value.Substring(0, value.Length - 4)); | ||
| 4506 | } | ||
| 4507 | } | ||
| 4508 | else | ||
| 4509 | { | ||
| 4510 | xEnvironment.SetAttributeValue("Value", value); | ||
| 4511 | } | ||
| 4512 | } | ||
| 4513 | |||
| 4514 | this.AddChildToParent("Component", xEnvironment, row, 3); | ||
| 4515 | } | ||
| 4516 | } | ||
| 4517 | |||
| 4518 | /// <summary> | ||
| 4519 | /// Decompile the Error table. | ||
| 4520 | /// </summary> | ||
| 4521 | /// <param name="table">The table to decompile.</param> | ||
| 4522 | private void DecompileErrorTable(Table table) | ||
| 4523 | { | ||
| 4524 | foreach (var row in table.Rows) | ||
| 4525 | { | ||
| 4526 | var xError = new XElement(Names.ErrorElement, | ||
| 4527 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4528 | new XAttribute("Message", row.FieldAsString(1))); | ||
| 4529 | |||
| 4530 | this.UIElement.Add(xError); | ||
| 4531 | } | ||
| 4532 | } | ||
| 4533 | |||
| 4534 | /// <summary> | ||
| 4535 | /// Decompile the EventMapping table. | ||
| 4536 | /// </summary> | ||
| 4537 | /// <param name="table">The table to decompile.</param> | ||
| 4538 | private void DecompileEventMappingTable(Table table) | ||
| 4539 | { | ||
| 4540 | foreach (var row in table.Rows) | ||
| 4541 | { | ||
| 4542 | var xSubscribe = new XElement(Names.SubscribeElement, | ||
| 4543 | new XAttribute("Event", row.FieldAsString(2)), | ||
| 4544 | new XAttribute("Attribute", row.FieldAsString(3))); | ||
| 4545 | |||
| 4546 | if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) | ||
| 4547 | { | ||
| 4548 | xControl.Add(xSubscribe); | ||
| 4549 | } | ||
| 4550 | else | ||
| 4551 | { | ||
| 4552 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); | ||
| 4553 | } | ||
| 4554 | } | ||
| 4555 | } | ||
| 4556 | |||
| 4557 | /// <summary> | ||
| 4558 | /// Decompile the Extension table. | ||
| 4559 | /// </summary> | ||
| 4560 | /// <param name="table">The table to decompile.</param> | ||
| 4561 | private void DecompileExtensionTable(Table table) | ||
| 4562 | { | ||
| 4563 | foreach (var row in table.Rows) | ||
| 4564 | { | ||
| 4565 | var xExtension = new XElement(Names.ExtensionElement, | ||
| 4566 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4567 | new XAttribute("Advertise", "yes")); | ||
| 4568 | |||
| 4569 | if (!row.IsColumnNull(3)) | ||
| 4570 | { | ||
| 4571 | if (this.TryGetIndexedElement("MIME", out var xMime, row.FieldAsString(3))) | ||
| 4572 | { | ||
| 4573 | xMime.SetAttributeValue("Default", "yes"); | ||
| 4574 | } | ||
| 4575 | else | ||
| 4576 | { | ||
| 4577 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); | ||
| 4578 | } | ||
| 4579 | } | ||
| 4580 | |||
| 4581 | if (!row.IsColumnNull(2)) | ||
| 4582 | { | ||
| 4583 | this.AddChildToParent("ProgId", xExtension, row, 2); | ||
| 4584 | } | ||
| 4585 | else | ||
| 4586 | { | ||
| 4587 | this.AddChildToParent("Component", xExtension, row, 1); | ||
| 4588 | } | ||
| 4589 | |||
| 4590 | this.IndexElement(row, xExtension); | ||
| 4591 | } | ||
| 4592 | } | ||
| 4593 | |||
| 4594 | /// <summary> | ||
| 4595 | /// Decompile the ExternalFiles table. | ||
| 4596 | /// </summary> | ||
| 4597 | /// <param name="table">The table to decompile.</param> | ||
| 4598 | private void DecompileExternalFilesTable(Table table) | ||
| 4599 | { | ||
| 4600 | foreach (var row in table.Rows) | ||
| 4601 | { | ||
| 4602 | var xExternalFile = new XElement(Names.ExternalFileElement, | ||
| 4603 | new XAttribute("File", row.FieldAsString(1)), | ||
| 4604 | new XAttribute("Source", row.FieldAsString(2))); | ||
| 4605 | |||
| 4606 | AddSymbolPaths(row, 3, xExternalFile); | ||
| 4607 | |||
| 4608 | if (!row.IsColumnNull(4) && !row.IsColumnNull(5)) | ||
| 4609 | { | ||
| 4610 | var ignoreOffsets = row.FieldAsString(4).Split(','); | ||
| 4611 | var ignoreLengths = row.FieldAsString(5).Split(','); | ||
| 4612 | |||
| 4613 | if (ignoreOffsets.Length == ignoreLengths.Length) | ||
| 4614 | { | ||
| 4615 | for (var i = 0; i < ignoreOffsets.Length; i++) | ||
| 4616 | { | ||
| 4617 | var xIgnoreRange = new XElement(Names.IgnoreRangeElement); | ||
| 4618 | |||
| 4619 | if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) | ||
| 4620 | { | ||
| 4621 | xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); | ||
| 4622 | } | ||
| 4623 | else | ||
| 4624 | { | ||
| 4625 | xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); | ||
| 4626 | } | ||
| 4627 | |||
| 4628 | if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) | ||
| 4629 | { | ||
| 4630 | xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); | ||
| 4631 | } | ||
| 4632 | else | ||
| 4633 | { | ||
| 4634 | xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); | ||
| 4635 | } | ||
| 4636 | |||
| 4637 | xExternalFile.Add(xIgnoreRange); | ||
| 4638 | } | ||
| 4639 | } | ||
| 4640 | else | ||
| 4641 | { | ||
| 4642 | // TODO: warn | ||
| 4643 | } | ||
| 4644 | } | ||
| 4645 | else if (!row.IsColumnNull(4) || !row.IsColumnNull(5)) | ||
| 4646 | { | ||
| 4647 | // TODO: warn about mismatch between columns | ||
| 4648 | } | ||
| 4649 | |||
| 4650 | // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable | ||
| 4651 | |||
| 4652 | if (!row.IsColumnNull(7)) | ||
| 4653 | { | ||
| 4654 | xExternalFile.SetAttributeValue("Order", row.FieldAsInteger(7)); | ||
| 4655 | } | ||
| 4656 | |||
| 4657 | this.AddChildToParent("ImageFamilies", xExternalFile, row, 0); | ||
| 4658 | this.IndexElement(row, xExternalFile); | ||
| 4659 | } | ||
| 4660 | } | ||
| 4661 | |||
| 4662 | /// <summary> | ||
| 4663 | /// Decompile the Feature table. | ||
| 4664 | /// </summary> | ||
| 4665 | /// <param name="table">The table to decompile.</param> | ||
| 4666 | private void DecompileFeatureTable(Table table) | ||
| 4667 | { | ||
| 4668 | var sortedFeatures = new SortedList<string, Row>(); | ||
| 4669 | |||
| 4670 | foreach (var row in table.Rows) | ||
| 4671 | { | ||
| 4672 | var feature = new XElement(Names.FeatureElement, | ||
| 4673 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4674 | row.IsColumnNull(2) ? null : new XAttribute("Title", row.FieldAsString(2)), | ||
| 4675 | row.IsColumnNull(3) ? null : new XAttribute("Description", row.FieldAsString(3)), | ||
| 4676 | new XAttribute("Level", row.FieldAsInteger(5)), | ||
| 4677 | row.IsColumnNull(6) ? null : new XAttribute("ConfigurableDirectory", row.FieldAsString(6))); | ||
| 4678 | |||
| 4679 | if (row.IsColumnNull(4)) | ||
| 4680 | { | ||
| 4681 | feature.SetAttributeValue("Display", "hidden"); | ||
| 4682 | } | ||
| 4683 | else | ||
| 4684 | { | ||
| 4685 | var display = row.FieldAsInteger(4); | ||
| 4686 | |||
| 4687 | if (0 == display) | ||
| 4688 | { | ||
| 4689 | feature.SetAttributeValue("Display", "hidden"); | ||
| 4690 | } | ||
| 4691 | else if (1 == display % 2) | ||
| 4692 | { | ||
| 4693 | feature.SetAttributeValue("Display", "expand"); | ||
| 4694 | } | ||
| 4695 | } | ||
| 4696 | |||
| 4697 | var attributes = row.FieldAsInteger(7); | ||
| 4698 | |||
| 4699 | if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) && WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) | ||
| 4700 | { | ||
| 4701 | // TODO: display a warning for setting favor local and follow parent together | ||
| 4702 | } | ||
| 4703 | else if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource)) | ||
| 4704 | { | ||
| 4705 | feature.SetAttributeValue("InstallDefault", "source"); | ||
| 4706 | } | ||
| 4707 | else if (WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) | ||
| 4708 | { | ||
| 4709 | feature.SetAttributeValue("InstallDefault", "followParent"); | ||
| 4710 | } | ||
| 4711 | |||
| 4712 | if (WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise)) | ||
| 4713 | { | ||
| 4714 | feature.SetAttributeValue("InstallDefault", "advertise"); | ||
| 4715 | } | ||
| 4716 | |||
| 4717 | if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) && | ||
| 4718 | WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) | ||
| 4719 | { | ||
| 4720 | this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no")); | ||
| 4721 | feature.SetAttributeValue("AllowAdvertise", "no"); | ||
| 4722 | } | ||
| 4723 | else if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise)) | ||
| 4724 | { | ||
| 4725 | feature.SetAttributeValue("AllowAdvertise", "no"); | ||
| 4726 | } | ||
| 4727 | else if (WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) | ||
| 4728 | { | ||
| 4729 | feature.SetAttributeValue("AllowAdvertise", "system"); | ||
| 4730 | } | ||
| 4731 | |||
| 4732 | if (WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent)) | ||
| 4733 | { | ||
| 4734 | feature.SetAttributeValue("Absent", "disallow"); | ||
| 4735 | } | ||
| 4736 | |||
| 4737 | this.IndexElement(row, feature); | ||
| 4738 | |||
| 4739 | // sort the features by their display column (and append the identifier to ensure unique keys) | ||
| 4740 | sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", row.FieldAsInteger(4), row[0]), row); | ||
| 4741 | } | ||
| 4742 | |||
| 4743 | // nest the features | ||
| 4744 | foreach (var row in sortedFeatures.Values) | ||
| 4745 | { | ||
| 4746 | var xFeature = this.GetIndexedElement("Feature", row.FieldAsString(0)); | ||
| 4747 | |||
| 4748 | if (row.IsColumnNull(1)) | ||
| 4749 | { | ||
| 4750 | this.RootElement.Add(xFeature); | ||
| 4751 | } | ||
| 4752 | else | ||
| 4753 | { | ||
| 4754 | if (this.TryGetIndexedElement("Feature", out var xParentFeature, row.FieldAsString(1))) | ||
| 4755 | { | ||
| 4756 | if (xParentFeature == xFeature) | ||
| 4757 | { | ||
| 4758 | // TODO: display a warning about self-nesting | ||
| 4759 | } | ||
| 4760 | else | ||
| 4761 | { | ||
| 4762 | xParentFeature.Add(xFeature); | ||
| 4763 | } | ||
| 4764 | } | ||
| 4765 | else | ||
| 4766 | { | ||
| 4767 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", row.FieldAsString(1), "Feature")); | ||
| 4768 | } | ||
| 4769 | } | ||
| 4770 | } | ||
| 4771 | } | ||
| 4772 | |||
| 4773 | /// <summary> | ||
| 4774 | /// Decompile the FeatureComponents table. | ||
| 4775 | /// </summary> | ||
| 4776 | /// <param name="table">The table to decompile.</param> | ||
| 4777 | private void DecompileFeatureComponentsTable(Table table) | ||
| 4778 | { | ||
| 4779 | foreach (var row in table.Rows) | ||
| 4780 | { | ||
| 4781 | var xComponentRef = new XElement(Names.ComponentRefElement, | ||
| 4782 | new XAttribute("Id", row.FieldAsString(1))); | ||
| 4783 | |||
| 4784 | this.AddChildToParent("Feature", xComponentRef, row, 0); | ||
| 4785 | this.IndexElement(row, xComponentRef); | ||
| 4786 | } | ||
| 4787 | } | ||
| 4788 | |||
| 4789 | /// <summary> | ||
| 4790 | /// Decompile the File table. | ||
| 4791 | /// </summary> | ||
| 4792 | /// <param name="table">The table to decompile.</param> | ||
| 4793 | private void DecompileFileTable(Table table) | ||
| 4794 | { | ||
| 4795 | foreach (FileRow fileRow in table.Rows) | ||
| 4796 | { | ||
| 4797 | var xFile = new XElement(Names.FileElement, | ||
| 4798 | new XAttribute("Id", fileRow.File), | ||
| 4799 | WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) ? new XAttribute("ReadOnly", "yes") : null, | ||
| 4800 | WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) ? new XAttribute("Hidden", "yes") : null, | ||
| 4801 | WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) ? new XAttribute("System", "yes") : null, | ||
| 4802 | WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) ? new XAttribute("Checksum", "yes") : null, | ||
| 4803 | WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital) ? new XAttribute("Vital", "no") : null, | ||
| 4804 | null != fileRow.Version && 0 < fileRow.Version.Length && !Char.IsDigit(fileRow.Version[0]) ? new XAttribute("CompanionFile", fileRow.Version) : null); | ||
| 4805 | |||
| 4806 | var names = this.BackendHelper.SplitMsiFileName(fileRow.FileName); | ||
| 4807 | if (null != names[0] && null != names[1]) | ||
| 4808 | { | ||
| 4809 | xFile.SetAttributeValue("ShortName", names[0]); | ||
| 4810 | xFile.SetAttributeValue("Name", names[1]); | ||
| 4811 | } | ||
| 4812 | else if (null != names[0]) | ||
| 4813 | { | ||
| 4814 | xFile.SetAttributeValue("Name", names[0]); | ||
| 4815 | } | ||
| 4816 | |||
| 4817 | if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) && | ||
| 4818 | WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) | ||
| 4819 | { | ||
| 4820 | // TODO: error | ||
| 4821 | } | ||
| 4822 | else if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed)) | ||
| 4823 | { | ||
| 4824 | xFile.SetAttributeValue("Compressed", "no"); | ||
| 4825 | } | ||
| 4826 | else if (WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) | ||
| 4827 | { | ||
| 4828 | xFile.SetAttributeValue("Compressed", "yes"); | ||
| 4829 | } | ||
| 4830 | |||
| 4831 | this.IndexElement(fileRow, xFile); | ||
| 4832 | } | ||
| 4833 | } | ||
| 4834 | |||
| 4835 | /// <summary> | ||
| 4836 | /// Decompile the FileSFPCatalog table. | ||
| 4837 | /// </summary> | ||
| 4838 | /// <param name="table">The table to decompile.</param> | ||
| 4839 | private void DecompileFileSFPCatalogTable(Table table) | ||
| 4840 | { | ||
| 4841 | foreach (var row in table.Rows) | ||
| 4842 | { | ||
| 4843 | var xSfpFile = new XElement(Names.SFPFileElement, | ||
| 4844 | new XAttribute("Id", row.FieldAsString(0))); | ||
| 4845 | |||
| 4846 | this.AddChildToParent("SFPCatalog", xSfpFile, row, 1); | ||
| 4847 | } | ||
| 4848 | } | ||
| 4849 | |||
| 4850 | /// <summary> | ||
| 4851 | /// Decompile the Font table. | ||
| 4852 | /// </summary> | ||
| 4853 | /// <param name="table">The table to decompile.</param> | ||
| 4854 | private void DecompileFontTable(Table table) | ||
| 4855 | { | ||
| 4856 | foreach (var row in table.Rows) | ||
| 4857 | { | ||
| 4858 | if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) | ||
| 4859 | { | ||
| 4860 | if (!row.IsColumnNull(1)) | ||
| 4861 | { | ||
| 4862 | xFile.SetAttributeValue("FontTitle", row.FieldAsString(1)); | ||
| 4863 | } | ||
| 4864 | else | ||
| 4865 | { | ||
| 4866 | xFile.SetAttributeValue("TrueType", "yes"); | ||
| 4867 | } | ||
| 4868 | } | ||
| 4869 | else | ||
| 4870 | { | ||
| 4871 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); | ||
| 4872 | } | ||
| 4873 | } | ||
| 4874 | } | ||
| 4875 | |||
| 4876 | /// <summary> | ||
| 4877 | /// Decompile the Icon table. | ||
| 4878 | /// </summary> | ||
| 4879 | /// <param name="table">The table to decompile.</param> | ||
| 4880 | private void DecompileIconTable(Table table) | ||
| 4881 | { | ||
| 4882 | foreach (var row in table.Rows) | ||
| 4883 | { | ||
| 4884 | var icon = new XElement(Names.IconElement, | ||
| 4885 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4886 | new XAttribute("SourceFile", row.FieldAsString(1))); | ||
| 4887 | |||
| 4888 | this.RootElement.Add(icon); | ||
| 4889 | } | ||
| 4890 | } | ||
| 4891 | |||
| 4892 | /// <summary> | ||
| 4893 | /// Decompile the ImageFamilies table. | ||
| 4894 | /// </summary> | ||
| 4895 | /// <param name="table">The table to decompile.</param> | ||
| 4896 | private void DecompileImageFamiliesTable(Table table) | ||
| 4897 | { | ||
| 4898 | foreach (var row in table.Rows) | ||
| 4899 | { | ||
| 4900 | var family = new XElement(Names.FamilyElement, | ||
| 4901 | new XAttribute("Name", row.FieldAsString(0)), | ||
| 4902 | row.IsColumnNull(1) ? null : new XAttribute("MediaSrcProp", row.FieldAsString(1)), | ||
| 4903 | row.IsColumnNull(2) ? null : new XAttribute("DiskId", row.FieldAsString(2)), | ||
| 4904 | row.IsColumnNull(3) ? null : new XAttribute("SequenceStart", row.FieldAsString(3)), | ||
| 4905 | row.IsColumnNull(4) ? null : new XAttribute("DiskPrompt", row.FieldAsString(4)), | ||
| 4906 | row.IsColumnNull(5) ? null : new XAttribute("VolumeLabel", row.FieldAsString(5))); | ||
| 4907 | |||
| 4908 | this.RootElement.Add(family); | ||
| 4909 | this.IndexElement(row, family); | ||
| 4910 | } | ||
| 4911 | } | ||
| 4912 | |||
| 4913 | /// <summary> | ||
| 4914 | /// Decompile the IniFile table. | ||
| 4915 | /// </summary> | ||
| 4916 | /// <param name="table">The table to decompile.</param> | ||
| 4917 | private void DecompileIniFileTable(Table table) | ||
| 4918 | { | ||
| 4919 | foreach (var row in table.Rows) | ||
| 4920 | { | ||
| 4921 | var xIniFile = new XElement(Names.IniFileElement, | ||
| 4922 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4923 | new XAttribute("Section", row.FieldAsString(3)), | ||
| 4924 | new XAttribute("Key", row.FieldAsString(4)), | ||
| 4925 | new XAttribute("Value", row.FieldAsString(5)), | ||
| 4926 | row.IsColumnNull(2) ? null : new XAttribute("Directory", row.FieldAsString(2))); | ||
| 4927 | |||
| 4928 | var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); | ||
| 4929 | |||
| 4930 | if (null != names[0]) | ||
| 4931 | { | ||
| 4932 | if (null == names[1]) | ||
| 4933 | { | ||
| 4934 | xIniFile.SetAttributeValue("Name", names[0]); | ||
| 4935 | } | ||
| 4936 | else | ||
| 4937 | { | ||
| 4938 | xIniFile.SetAttributeValue("ShortName", names[0]); | ||
| 4939 | } | ||
| 4940 | } | ||
| 4941 | |||
| 4942 | if (null != names[1]) | ||
| 4943 | { | ||
| 4944 | xIniFile.SetAttributeValue("Name", names[1]); | ||
| 4945 | } | ||
| 4946 | |||
| 4947 | switch (row.FieldAsInteger(6)) | ||
| 4948 | { | ||
| 4949 | case WindowsInstallerConstants.MsidbIniFileActionAddLine: | ||
| 4950 | xIniFile.SetAttributeValue("Action", "addLine"); | ||
| 4951 | break; | ||
| 4952 | case WindowsInstallerConstants.MsidbIniFileActionCreateLine: | ||
| 4953 | xIniFile.SetAttributeValue("Action", "createLine"); | ||
| 4954 | break; | ||
| 4955 | case WindowsInstallerConstants.MsidbIniFileActionAddTag: | ||
| 4956 | xIniFile.SetAttributeValue("Action", "addTag"); | ||
| 4957 | break; | ||
| 4958 | default: | ||
| 4959 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); | ||
| 4960 | break; | ||
| 4961 | } | ||
| 4962 | |||
| 4963 | this.AddChildToParent("Component", xIniFile, row, 7); | ||
| 4964 | } | ||
| 4965 | } | ||
| 4966 | |||
| 4967 | /// <summary> | ||
| 4968 | /// Decompile the IniLocator table. | ||
| 4969 | /// </summary> | ||
| 4970 | /// <param name="table">The table to decompile.</param> | ||
| 4971 | private void DecompileIniLocatorTable(Table table) | ||
| 4972 | { | ||
| 4973 | foreach (var row in table.Rows) | ||
| 4974 | { | ||
| 4975 | var xIniFileSearch = new XElement(Names.IniFileSearchElement, | ||
| 4976 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 4977 | new XAttribute("Section", row.FieldAsString(2)), | ||
| 4978 | new XAttribute("Key", row.FieldAsString(3)), | ||
| 4979 | row.IsColumnNull(4) || row.FieldAsInteger(4) == 0 ? null : new XAttribute("Field", row.FieldAsInteger(4))); | ||
| 4980 | |||
| 4981 | var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); | ||
| 4982 | if (null != names[0] && null != names[1]) | ||
| 4983 | { | ||
| 4984 | xIniFileSearch.SetAttributeValue("ShortName", names[0]); | ||
| 4985 | xIniFileSearch.SetAttributeValue("Name", names[1]); | ||
| 4986 | } | ||
| 4987 | else if (null != names[0]) | ||
| 4988 | { | ||
| 4989 | xIniFileSearch.SetAttributeValue("Name", names[0]); | ||
| 4990 | } | ||
| 4991 | |||
| 4992 | if (!row.IsColumnNull(5)) | ||
| 4993 | { | ||
| 4994 | switch (row.FieldAsInteger(5)) | ||
| 4995 | { | ||
| 4996 | case WindowsInstallerConstants.MsidbLocatorTypeDirectory: | ||
| 4997 | xIniFileSearch.SetAttributeValue("Type", "directory"); | ||
| 4998 | break; | ||
| 4999 | case WindowsInstallerConstants.MsidbLocatorTypeFileName: | ||
| 5000 | // this is the default value | ||
| 5001 | break; | ||
| 5002 | case WindowsInstallerConstants.MsidbLocatorTypeRawValue: | ||
| 5003 | xIniFileSearch.SetAttributeValue("Type", "raw"); | ||
| 5004 | break; | ||
| 5005 | default: | ||
| 5006 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); | ||
| 5007 | break; | ||
| 5008 | } | ||
| 5009 | } | ||
| 5010 | |||
| 5011 | this.IndexElement(row, xIniFileSearch); | ||
| 5012 | } | ||
| 5013 | } | ||
| 5014 | |||
| 5015 | /// <summary> | ||
| 5016 | /// Decompile the IsolatedComponent table. | ||
| 5017 | /// </summary> | ||
| 5018 | /// <param name="table">The table to decompile.</param> | ||
| 5019 | private void DecompileIsolatedComponentTable(Table table) | ||
| 5020 | { | ||
| 5021 | foreach (var row in table.Rows) | ||
| 5022 | { | ||
| 5023 | var xIsolateComponent = new XElement(Names.IsolateComponentElement, | ||
| 5024 | new XAttribute("Shared", row.FieldAsString(0))); | ||
| 5025 | |||
| 5026 | this.AddChildToParent("Component", xIsolateComponent, row, 1); | ||
| 5027 | } | ||
| 5028 | } | ||
| 5029 | |||
| 5030 | /// <summary> | ||
| 5031 | /// Decompile the LaunchCondition table. | ||
| 5032 | /// </summary> | ||
| 5033 | /// <param name="table">The table to decompile.</param> | ||
| 5034 | private void DecompileLaunchConditionTable(Table table) | ||
| 5035 | { | ||
| 5036 | foreach (var row in table.Rows) | ||
| 5037 | { | ||
| 5038 | if (WixUpgradeConstants.DowngradePreventedCondition == row.FieldAsString(0) || WixUpgradeConstants.UpgradePreventedCondition == row.FieldAsString(0)) | ||
| 5039 | { | ||
| 5040 | continue; // MajorUpgrade rows processed in FinalizeUpgradeTable | ||
| 5041 | } | ||
| 5042 | |||
| 5043 | var condition = new XElement(Names.LaunchElement, | ||
| 5044 | new XAttribute("Condition", row.FieldAsString(0)), | ||
| 5045 | new XAttribute("Message", row.FieldAsString(1))); | ||
| 5046 | |||
| 5047 | this.RootElement.Add(condition); | ||
| 5048 | } | ||
| 5049 | } | ||
| 5050 | |||
| 5051 | /// <summary> | ||
| 5052 | /// Decompile the ListBox table. | ||
| 5053 | /// </summary> | ||
| 5054 | /// <param name="table">The table to decompile.</param> | ||
| 5055 | private void DecompileListBoxTable(Table table) | ||
| 5056 | { | ||
| 5057 | // sort the list boxes by their property and order | ||
| 5058 | var listBoxRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); | ||
| 5059 | |||
| 5060 | XElement xListBox = null; | ||
| 5061 | foreach (Row row in listBoxRows) | ||
| 5062 | { | ||
| 5063 | if (null == xListBox || row.FieldAsString(0) != xListBox.Attribute("Property")?.Value) | ||
| 5064 | { | ||
| 5065 | xListBox = new XElement(Names.ListBoxElement, | ||
| 5066 | new XAttribute("Property", row.FieldAsString(0))); | ||
| 5067 | |||
| 5068 | this.UIElement.Add(xListBox); | ||
| 5069 | } | ||
| 5070 | |||
| 5071 | var listItem = new XElement(Names.ListItemElement, | ||
| 5072 | new XAttribute("Value", row.FieldAsString(2)), | ||
| 5073 | row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); | ||
| 5074 | |||
| 5075 | xListBox.Add(listItem); | ||
| 5076 | } | ||
| 5077 | } | ||
| 5078 | |||
| 5079 | /// <summary> | ||
| 5080 | /// Decompile the ListView table. | ||
| 5081 | /// </summary> | ||
| 5082 | /// <param name="table">The table to decompile.</param> | ||
| 5083 | private void DecompileListViewTable(Table table) | ||
| 5084 | { | ||
| 5085 | // sort the list views by their property and order | ||
| 5086 | var listViewRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); | ||
| 5087 | |||
| 5088 | XElement xListView = null; | ||
| 5089 | foreach (var row in listViewRows) | ||
| 5090 | { | ||
| 5091 | if (null == xListView || row.FieldAsString(0) != xListView.Attribute("Property")?.Value) | ||
| 5092 | { | ||
| 5093 | xListView = new XElement(Names.ListViewElement, | ||
| 5094 | new XAttribute("Property", row.FieldAsString(0))); | ||
| 5095 | |||
| 5096 | this.UIElement.Add(xListView); | ||
| 5097 | } | ||
| 5098 | |||
| 5099 | var listItem = new XElement(Names.ListItemElement, | ||
| 5100 | new XAttribute("Value", row.FieldAsString(2)), | ||
| 5101 | row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)), | ||
| 5102 | row.IsColumnNull(4) ? null : new XAttribute("Icon", row.FieldAsString(4))); | ||
| 5103 | |||
| 5104 | xListView.Add(listItem); | ||
| 5105 | } | ||
| 5106 | } | ||
| 5107 | |||
| 5108 | /// <summary> | ||
| 5109 | /// Decompile the LockPermissions table. | ||
| 5110 | /// </summary> | ||
| 5111 | /// <param name="table">The table to decompile.</param> | ||
| 5112 | private void DecompileLockPermissionsTable(Table table) | ||
| 5113 | { | ||
| 5114 | foreach (var row in table.Rows) | ||
| 5115 | { | ||
| 5116 | var xPermission = new XElement(Names.PermissionElement, | ||
| 5117 | row.IsColumnNull(2) ? null : new XAttribute("Domain", row.FieldAsString(2)), | ||
| 5118 | new XAttribute("User", row.FieldAsString(3))); | ||
| 5119 | |||
| 5120 | string[] specialPermissions; | ||
| 5121 | |||
| 5122 | switch (row.FieldAsString(1)) | ||
| 5123 | { | ||
| 5124 | case "CreateFolder": | ||
| 5125 | specialPermissions = LockPermissionConstants.FolderPermissions; | ||
| 5126 | break; | ||
| 5127 | case "File": | ||
| 5128 | specialPermissions = LockPermissionConstants.FilePermissions; | ||
| 5129 | break; | ||
| 5130 | case "Registry": | ||
| 5131 | specialPermissions = LockPermissionConstants.RegistryPermissions; | ||
| 5132 | break; | ||
| 5133 | default: | ||
| 5134 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 5135 | return; | ||
| 5136 | } | ||
| 5137 | |||
| 5138 | var permissionBits = row.FieldAsInteger(4); | ||
| 5139 | for (var i = 0; i < 32; i++) | ||
| 5140 | { | ||
| 5141 | if (0 != ((permissionBits >> i) & 1)) | ||
| 5142 | { | ||
| 5143 | string name = null; | ||
| 5144 | |||
| 5145 | if (specialPermissions.Length > i) | ||
| 5146 | { | ||
| 5147 | name = specialPermissions[i]; | ||
| 5148 | } | ||
| 5149 | else if (16 > i && specialPermissions.Length <= i) | ||
| 5150 | { | ||
| 5151 | name = "SpecificRightsAll"; | ||
| 5152 | } | ||
| 5153 | else if (28 > i && LockPermissionConstants.StandardPermissions.Length > (i - 16)) | ||
| 5154 | { | ||
| 5155 | name = LockPermissionConstants.StandardPermissions[i - 16]; | ||
| 5156 | } | ||
| 5157 | else if (0 <= (i - 28) && LockPermissionConstants.GenericPermissions.Length > (i - 28)) | ||
| 5158 | { | ||
| 5159 | name = LockPermissionConstants.GenericPermissions[i - 28]; | ||
| 5160 | } | ||
| 5161 | |||
| 5162 | if (null == name) | ||
| 5163 | { | ||
| 5164 | this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); | ||
| 5165 | } | ||
| 5166 | else | ||
| 5167 | { | ||
| 5168 | switch (name) | ||
| 5169 | { | ||
| 5170 | case "Append": | ||
| 5171 | case "ChangePermission": | ||
| 5172 | case "CreateChild": | ||
| 5173 | case "CreateFile": | ||
| 5174 | case "CreateLink": | ||
| 5175 | case "CreateSubkeys": | ||
| 5176 | case "Delete": | ||
| 5177 | case "DeleteChild": | ||
| 5178 | case "EnumerateSubkeys": | ||
| 5179 | case "Execute": | ||
| 5180 | case "FileAllRights": | ||
| 5181 | case "GenericAll": | ||
| 5182 | case "GenericExecute": | ||
| 5183 | case "GenericRead": | ||
| 5184 | case "GenericWrite": | ||
| 5185 | case "Notify": | ||
| 5186 | case "Read": | ||
| 5187 | case "ReadAttributes": | ||
| 5188 | case "ReadExtendedAttributes": | ||
| 5189 | case "ReadPermission": | ||
| 5190 | case "SpecificRightsAll": | ||
| 5191 | case "Synchronize": | ||
| 5192 | case "TakeOwnership": | ||
| 5193 | case "Traverse": | ||
| 5194 | case "Write": | ||
| 5195 | case "WriteAttributes": | ||
| 5196 | case "WriteExtendedAttributes": | ||
| 5197 | xPermission.SetAttributeValue(name, "yes"); | ||
| 5198 | break; | ||
| 5199 | default: | ||
| 5200 | throw new InvalidOperationException($"Unknown permission attribute '{name}'."); | ||
| 5201 | } | ||
| 5202 | } | ||
| 5203 | } | ||
| 5204 | } | ||
| 5205 | |||
| 5206 | this.IndexElement(row, xPermission); | ||
| 5207 | } | ||
| 5208 | } | ||
| 5209 | |||
| 5210 | /// <summary> | ||
| 5211 | /// Decompile the Media table. | ||
| 5212 | /// </summary> | ||
| 5213 | /// <param name="table">The table to decompile.</param> | ||
| 5214 | private void DecompileMediaTable(Table table) | ||
| 5215 | { | ||
| 5216 | foreach (MediaRow mediaRow in table.Rows) | ||
| 5217 | { | ||
| 5218 | var xMedia = new XElement(Names.MediaElement, | ||
| 5219 | new XAttribute("Id", mediaRow.DiskId), | ||
| 5220 | mediaRow.DiskPrompt == null ? null : new XAttribute("DiskPrompt", mediaRow.DiskPrompt), | ||
| 5221 | mediaRow.VolumeLabel == null ? null : new XAttribute("VolumeLabel", mediaRow.VolumeLabel)); | ||
| 5222 | |||
| 5223 | if (null != mediaRow.Cabinet) | ||
| 5224 | { | ||
| 5225 | var cabinet = mediaRow.Cabinet; | ||
| 5226 | |||
| 5227 | if (cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
| 5228 | { | ||
| 5229 | xMedia.SetAttributeValue("EmbedCab", "yes"); | ||
| 5230 | cabinet = cabinet.Substring(1); | ||
| 5231 | } | ||
| 5232 | |||
| 5233 | xMedia.SetAttributeValue("Cabinet", cabinet); | ||
| 5234 | } | ||
| 5235 | |||
| 5236 | this.RootElement.Add(xMedia); | ||
| 5237 | this.IndexElement(mediaRow, xMedia); | ||
| 5238 | } | ||
| 5239 | } | ||
| 5240 | |||
| 5241 | /// <summary> | ||
| 5242 | /// Decompile the MIME table. | ||
| 5243 | /// </summary> | ||
| 5244 | /// <param name="table">The table to decompile.</param> | ||
| 5245 | private void DecompileMIMETable(Table table) | ||
| 5246 | { | ||
| 5247 | foreach (var row in table.Rows) | ||
| 5248 | { | ||
| 5249 | var mime = new XElement(Names.MIMEElement, | ||
| 5250 | new XAttribute("ContentType", row.FieldAsString(0)), | ||
| 5251 | row.IsColumnNull(2) ? null : new XAttribute("Class", row.FieldAsString(2))); | ||
| 5252 | |||
| 5253 | this.IndexElement(row, mime); | ||
| 5254 | } | ||
| 5255 | } | ||
| 5256 | |||
| 5257 | /// <summary> | ||
| 5258 | /// Decompile the ModuleConfiguration table. | ||
| 5259 | /// </summary> | ||
| 5260 | /// <param name="table">The table to decompile.</param> | ||
| 5261 | private void DecompileModuleConfigurationTable(Table table) | ||
| 5262 | { | ||
| 5263 | foreach (var row in table.Rows) | ||
| 5264 | { | ||
| 5265 | var configuration = new XElement(Names.ConfigurationElement, | ||
| 5266 | new XAttribute("Name", row.FieldAsString(0)), | ||
| 5267 | XAttributeIfNotNull("Type", row, 2), | ||
| 5268 | XAttributeIfNotNull("ContextData", row, 3), | ||
| 5269 | XAttributeIfNotNull("DefaultValue", row, 4), | ||
| 5270 | XAttributeIfNotNull("DisplayName", row, 6), | ||
| 5271 | XAttributeIfNotNull("Description", row, 7), | ||
| 5272 | XAttributeIfNotNull("HelpLocation", row, 8), | ||
| 5273 | XAttributeIfNotNull("HelpKeyword", row, 9)); | ||
| 5274 | |||
| 5275 | switch (row.FieldAsInteger(1)) | ||
| 5276 | { | ||
| 5277 | case 0: | ||
| 5278 | configuration.SetAttributeValue("Format", "Text"); | ||
| 5279 | break; | ||
| 5280 | case 1: | ||
| 5281 | configuration.SetAttributeValue("Format", "Key"); | ||
| 5282 | break; | ||
| 5283 | case 2: | ||
| 5284 | configuration.SetAttributeValue("Format", "Integer"); | ||
| 5285 | break; | ||
| 5286 | case 3: | ||
| 5287 | configuration.SetAttributeValue("Format", "Bitfield"); | ||
| 5288 | break; | ||
| 5289 | default: | ||
| 5290 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 5291 | break; | ||
| 5292 | } | ||
| 5293 | |||
| 5294 | if (!row.IsColumnNull(5)) | ||
| 5295 | { | ||
| 5296 | var attributes = row.FieldAsInteger(5); | ||
| 5297 | |||
| 5298 | if (WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan)) | ||
| 5299 | { | ||
| 5300 | configuration.SetAttributeValue("KeyNoOrphan", "yes"); | ||
| 5301 | } | ||
| 5302 | |||
| 5303 | if (WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable)) | ||
| 5304 | { | ||
| 5305 | configuration.SetAttributeValue("NonNullable", "yes"); | ||
| 5306 | } | ||
| 5307 | |||
| 5308 | if (3 < attributes) | ||
| 5309 | { | ||
| 5310 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); | ||
| 5311 | } | ||
| 5312 | } | ||
| 5313 | |||
| 5314 | this.RootElement.Add(configuration); | ||
| 5315 | } | ||
| 5316 | } | ||
| 5317 | |||
| 5318 | /// <summary> | ||
| 5319 | /// Decompile the ModuleDependency table. | ||
| 5320 | /// </summary> | ||
| 5321 | /// <param name="table">The table to decompile.</param> | ||
| 5322 | private void DecompileModuleDependencyTable(Table table) | ||
| 5323 | { | ||
| 5324 | foreach (var row in table.Rows) | ||
| 5325 | { | ||
| 5326 | var xDependency = new XElement(Names.DependencyElement, | ||
| 5327 | new XAttribute("RequiredId", row.FieldAsString(2)), | ||
| 5328 | new XAttribute("RequiredLanguage", row.FieldAsString(3)), | ||
| 5329 | XAttributeIfNotNull("RequiredVersion", row, 4)); | ||
| 5330 | |||
| 5331 | this.RootElement.Add(xDependency); | ||
| 5332 | } | ||
| 5333 | } | ||
| 5334 | |||
| 5335 | /// <summary> | ||
| 5336 | /// Decompile the ModuleExclusion table. | ||
| 5337 | /// </summary> | ||
| 5338 | /// <param name="table">The table to decompile.</param> | ||
| 5339 | private void DecompileModuleExclusionTable(Table table) | ||
| 5340 | { | ||
| 5341 | foreach (var row in table.Rows) | ||
| 5342 | { | ||
| 5343 | var xExclusion = new XElement(Names.ExclusionElement, | ||
| 5344 | new XAttribute("ExcludedId", row.FieldAsString(2)), | ||
| 5345 | XAttributeIfNotNull("ExcludedMinVersion", row, 4), | ||
| 5346 | XAttributeIfNotNull("ExcludedMaxVersion", row, 5)); | ||
| 5347 | |||
| 5348 | var excludedLanguage = row.FieldAsInteger(3); | ||
| 5349 | if (0 < excludedLanguage) | ||
| 5350 | { | ||
| 5351 | xExclusion.SetAttributeValue("ExcludeLanguage", excludedLanguage); | ||
| 5352 | } | ||
| 5353 | else if (0 > excludedLanguage) | ||
| 5354 | { | ||
| 5355 | xExclusion.SetAttributeValue("ExcludeExceptLanguage", -excludedLanguage); | ||
| 5356 | } | ||
| 5357 | |||
| 5358 | this.RootElement.Add(xExclusion); | ||
| 5359 | } | ||
| 5360 | } | ||
| 5361 | |||
| 5362 | /// <summary> | ||
| 5363 | /// Decompile the ModuleIgnoreTable table. | ||
| 5364 | /// </summary> | ||
| 5365 | /// <param name="table">The table to decompile.</param> | ||
| 5366 | private void DecompileModuleIgnoreTableTable(Table table) | ||
| 5367 | { | ||
| 5368 | foreach (var row in table.Rows) | ||
| 5369 | { | ||
| 5370 | var tableName = row.FieldAsString(0); | ||
| 5371 | |||
| 5372 | // the linker automatically adds a ModuleIgnoreTable row for some tables | ||
| 5373 | if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName) | ||
| 5374 | { | ||
| 5375 | var xIgnoreTable = new XElement(Names.IgnoreTableElement, | ||
| 5376 | new XAttribute("Id", tableName)); | ||
| 5377 | |||
| 5378 | this.RootElement.Add(xIgnoreTable); | ||
| 5379 | } | ||
| 5380 | } | ||
| 5381 | } | ||
| 5382 | |||
| 5383 | /// <summary> | ||
| 5384 | /// Decompile the ModuleSignature table. | ||
| 5385 | /// </summary> | ||
| 5386 | /// <param name="table">The table to decompile.</param> | ||
| 5387 | private void DecompileModuleSignatureTable(Table table) | ||
| 5388 | { | ||
| 5389 | if (1 == table.Rows.Count) | ||
| 5390 | { | ||
| 5391 | var row = table.Rows[0]; | ||
| 5392 | |||
| 5393 | this.RootElement.SetAttributeValue("Id", row.FieldAsString(0)); | ||
| 5394 | // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability) | ||
| 5395 | this.RootElement.SetAttributeValue("Language", row.FieldAsString(1)); | ||
| 5396 | this.RootElement.SetAttributeValue("Version", row.FieldAsString(2)); | ||
| 5397 | } | ||
| 5398 | else | ||
| 5399 | { | ||
| 5400 | // TODO: warn | ||
| 5401 | } | ||
| 5402 | } | ||
| 5403 | |||
| 5404 | /// <summary> | ||
| 5405 | /// Decompile the ModuleSubstitution table. | ||
| 5406 | /// </summary> | ||
| 5407 | /// <param name="table">The table to decompile.</param> | ||
| 5408 | private void DecompileModuleSubstitutionTable(Table table) | ||
| 5409 | { | ||
| 5410 | foreach (var row in table.Rows) | ||
| 5411 | { | ||
| 5412 | var xSubstitution = new XElement(Names.SubstitutionElement, | ||
| 5413 | new XAttribute("Table", row.FieldAsString(0)), | ||
| 5414 | new XAttribute("Row", row.FieldAsString(1)), | ||
| 5415 | new XAttribute("Column", row.FieldAsString(2)), | ||
| 5416 | XAttributeIfNotNull("Value", row, 3)); | ||
| 5417 | |||
| 5418 | this.RootElement.Add(xSubstitution); | ||
| 5419 | } | ||
| 5420 | } | ||
| 5421 | |||
| 5422 | /// <summary> | ||
| 5423 | /// Decompile the MoveFile table. | ||
| 5424 | /// </summary> | ||
| 5425 | /// <param name="table">The table to decompile.</param> | ||
| 5426 | private void DecompileMoveFileTable(Table table) | ||
| 5427 | { | ||
| 5428 | foreach (var row in table.Rows) | ||
| 5429 | { | ||
| 5430 | var xCopyFile = new XElement(Names.CopyFileElement, | ||
| 5431 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 5432 | XAttributeIfNotNull("SourceName", row, 2)); | ||
| 5433 | |||
| 5434 | if (!row.IsColumnNull(3)) | ||
| 5435 | { | ||
| 5436 | var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); | ||
| 5437 | if (null != names[0] && null != names[1]) | ||
| 5438 | { | ||
| 5439 | xCopyFile.SetAttributeValue("DestinationShortName", names[0]); | ||
| 5440 | xCopyFile.SetAttributeValue("DestinationName", names[1]); | ||
| 5441 | } | ||
| 5442 | else if (null != names[0]) | ||
| 5443 | { | ||
| 5444 | xCopyFile.SetAttributeValue("DestinationName", names[0]); | ||
| 5445 | } | ||
| 5446 | } | ||
| 5447 | |||
| 5448 | // source/destination directory/property is set in FinalizeDuplicateMoveFileTables | ||
| 5449 | |||
| 5450 | switch (row.FieldAsInteger(6)) | ||
| 5451 | { | ||
| 5452 | case 0: | ||
| 5453 | break; | ||
| 5454 | case WindowsInstallerConstants.MsidbMoveFileOptionsMove: | ||
| 5455 | xCopyFile.SetAttributeValue("Delete", "yes"); | ||
| 5456 | break; | ||
| 5457 | default: | ||
| 5458 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); | ||
| 5459 | break; | ||
| 5460 | } | ||
| 5461 | |||
| 5462 | this.AddChildToParent("Component", xCopyFile, row, 1); | ||
| 5463 | this.IndexElement(row, xCopyFile); | ||
| 5464 | } | ||
| 5465 | } | ||
| 5466 | |||
| 5467 | /// <summary> | ||
| 5468 | /// Decompile the MsiDigitalCertificate table. | ||
| 5469 | /// </summary> | ||
| 5470 | /// <param name="table">The table to decompile.</param> | ||
| 5471 | private void DecompileMsiDigitalCertificateTable(Table table) | ||
| 5472 | { | ||
| 5473 | foreach (var row in table.Rows) | ||
| 5474 | { | ||
| 5475 | var xDigitalCertificate = new XElement(Names.DigitalCertificateElement, | ||
| 5476 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 5477 | new XAttribute("SourceFile", row.FieldAsString(1))); | ||
| 5478 | |||
| 5479 | this.IndexElement(row, xDigitalCertificate); | ||
| 5480 | } | ||
| 5481 | } | ||
| 5482 | |||
| 5483 | /// <summary> | ||
| 5484 | /// Decompile the MsiDigitalSignature table. | ||
| 5485 | /// </summary> | ||
| 5486 | /// <param name="table">The table to decompile.</param> | ||
| 5487 | private void DecompileMsiDigitalSignatureTable(Table table) | ||
| 5488 | { | ||
| 5489 | foreach (var row in table.Rows) | ||
| 5490 | { | ||
| 5491 | var xDigitalSignature = new XElement(Names.DigitalSignatureElement, | ||
| 5492 | XAttributeIfNotNull("SourceFile", row, 3)); | ||
| 5493 | |||
| 5494 | this.AddChildToParent("MsiDigitalCertificate", xDigitalSignature, row, 2); | ||
| 5495 | |||
| 5496 | if (this.TryGetIndexedElement(row.FieldAsString(0), out var xParentElement, row.FieldAsString(1))) | ||
| 5497 | { | ||
| 5498 | xParentElement.Add(xDigitalSignature); | ||
| 5499 | } | ||
| 5500 | else | ||
| 5501 | { | ||
| 5502 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", row.FieldAsString(1), row.FieldAsString(0))); | ||
| 5503 | } | ||
| 5504 | } | ||
| 5505 | } | ||
| 5506 | |||
| 5507 | /// <summary> | ||
| 5508 | /// Decompile the MsiEmbeddedChainer table. | ||
| 5509 | /// </summary> | ||
| 5510 | /// <param name="table">The table to decompile.</param> | ||
| 5511 | private void DecompileMsiEmbeddedChainerTable(Table table) | ||
| 5512 | { | ||
| 5513 | foreach (var row in table.Rows) | ||
| 5514 | { | ||
| 5515 | var xEmbeddedChainer = new XElement(Names.EmbeddedChainerElement, | ||
| 5516 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 5517 | new XAttribute("Condition", row.FieldAsString(1)), | ||
| 5518 | XAttributeIfNotNull("CommandLine", row, 2)); | ||
| 5519 | |||
| 5520 | switch (row.FieldAsInteger(4)) | ||
| 5521 | { | ||
| 5522 | case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: | ||
| 5523 | xEmbeddedChainer.SetAttributeValue("BinarySource", row.FieldAsString(3)); | ||
| 5524 | break; | ||
| 5525 | case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: | ||
| 5526 | xEmbeddedChainer.SetAttributeValue("FileSource", row.FieldAsString(3)); | ||
| 5527 | break; | ||
| 5528 | case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeProperty: | ||
| 5529 | xEmbeddedChainer.SetAttributeValue("PropertySource", row.FieldAsString(3)); | ||
| 5530 | break; | ||
| 5531 | default: | ||
| 5532 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
| 5533 | break; | ||
| 5534 | } | ||
| 5535 | |||
| 5536 | this.RootElement.Add(xEmbeddedChainer); | ||
| 5537 | } | ||
| 5538 | } | ||
| 5539 | |||
| 5540 | /// <summary> | ||
| 5541 | /// Decompile the MsiEmbeddedUI table. | ||
| 5542 | /// </summary> | ||
| 5543 | /// <param name="table">The table to decompile.</param> | ||
| 5544 | private void DecompileMsiEmbeddedUITable(Table table) | ||
| 5545 | { | ||
| 5546 | var xEmbeddedUI = new XElement(Names.EmbeddedUIElement); | ||
| 5547 | |||
| 5548 | var foundEmbeddedUI = false; | ||
| 5549 | var foundEmbeddedResources = false; | ||
| 5550 | |||
| 5551 | foreach (var row in table.Rows) | ||
| 5552 | { | ||
| 5553 | var attributes = row.FieldAsInteger(2); | ||
| 5554 | |||
| 5555 | if (WindowsInstallerConstants.MsidbEmbeddedUI == (attributes & WindowsInstallerConstants.MsidbEmbeddedUI)) | ||
| 5556 | { | ||
| 5557 | if (foundEmbeddedUI) | ||
| 5558 | { | ||
| 5559 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); | ||
| 5560 | } | ||
| 5561 | else | ||
| 5562 | { | ||
| 5563 | xEmbeddedUI.SetAttributeValue("Id", row.FieldAsString(0)); | ||
| 5564 | xEmbeddedUI.SetAttributeValue("Name", row.FieldAsString(1)); | ||
| 5565 | |||
| 5566 | var messageFilter = row.FieldAsInteger(3); | ||
| 5567 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT)) | ||
| 5568 | { | ||
| 5569 | xEmbeddedUI.SetAttributeValue("IgnoreFatalExit", "yes"); | ||
| 5570 | } | ||
| 5571 | |||
| 5572 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ERROR)) | ||
| 5573 | { | ||
| 5574 | xEmbeddedUI.SetAttributeValue("IgnoreError", "yes"); | ||
| 5575 | } | ||
| 5576 | |||
| 5577 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_WARNING)) | ||
| 5578 | { | ||
| 5579 | xEmbeddedUI.SetAttributeValue("IgnoreWarning", "yes"); | ||
| 5580 | } | ||
| 5581 | |||
| 5582 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_USER)) | ||
| 5583 | { | ||
| 5584 | xEmbeddedUI.SetAttributeValue("IgnoreUser", "yes"); | ||
| 5585 | } | ||
| 5586 | |||
| 5587 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INFO)) | ||
| 5588 | { | ||
| 5589 | xEmbeddedUI.SetAttributeValue("IgnoreInfo", "yes"); | ||
| 5590 | } | ||
| 5591 | |||
| 5592 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE)) | ||
| 5593 | { | ||
| 5594 | xEmbeddedUI.SetAttributeValue("IgnoreFilesInUse", "yes"); | ||
| 5595 | } | ||
| 5596 | |||
| 5597 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE)) | ||
| 5598 | { | ||
| 5599 | xEmbeddedUI.SetAttributeValue("IgnoreResolveSource", "yes"); | ||
| 5600 | } | ||
| 5601 | |||
| 5602 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE)) | ||
| 5603 | { | ||
| 5604 | xEmbeddedUI.SetAttributeValue("IgnoreOutOfDiskSpace", "yes"); | ||
| 5605 | } | ||
| 5606 | |||
| 5607 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART)) | ||
| 5608 | { | ||
| 5609 | xEmbeddedUI.SetAttributeValue("IgnoreActionStart", "yes"); | ||
| 5610 | } | ||
| 5611 | |||
| 5612 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA)) | ||
| 5613 | { | ||
| 5614 | xEmbeddedUI.SetAttributeValue("IgnoreActionData", "yes"); | ||
| 5615 | } | ||
| 5616 | |||
| 5617 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS)) | ||
| 5618 | { | ||
| 5619 | xEmbeddedUI.SetAttributeValue("IgnoreProgress", "yes"); | ||
| 5620 | } | ||
| 5621 | |||
| 5622 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA)) | ||
| 5623 | { | ||
| 5624 | xEmbeddedUI.SetAttributeValue("IgnoreCommonData", "yes"); | ||
| 5625 | } | ||
| 5626 | |||
| 5627 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE)) | ||
| 5628 | { | ||
| 5629 | xEmbeddedUI.SetAttributeValue("IgnoreInitialize", "yes"); | ||
| 5630 | } | ||
| 5631 | |||
| 5632 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE)) | ||
| 5633 | { | ||
| 5634 | xEmbeddedUI.SetAttributeValue("IgnoreTerminate", "yes"); | ||
| 5635 | } | ||
| 5636 | |||
| 5637 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG)) | ||
| 5638 | { | ||
| 5639 | xEmbeddedUI.SetAttributeValue("IgnoreShowDialog", "yes"); | ||
| 5640 | } | ||
| 5641 | |||
| 5642 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE)) | ||
| 5643 | { | ||
| 5644 | xEmbeddedUI.SetAttributeValue("IgnoreRMFilesInUse", "yes"); | ||
| 5645 | } | ||
| 5646 | |||
| 5647 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART)) | ||
| 5648 | { | ||
| 5649 | xEmbeddedUI.SetAttributeValue("IgnoreInstallStart", "yes"); | ||
| 5650 | } | ||
| 5651 | |||
| 5652 | if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND)) | ||
| 5653 | { | ||
| 5654 | xEmbeddedUI.SetAttributeValue("IgnoreInstallEnd", "yes"); | ||
| 5655 | } | ||
| 5656 | |||
| 5657 | if (WindowsInstallerConstants.MsidbEmbeddedHandlesBasic == (attributes & WindowsInstallerConstants.MsidbEmbeddedHandlesBasic)) | ||
| 5658 | { | ||
| 5659 | xEmbeddedUI.SetAttributeValue("SupportBasicUI", "yes"); | ||
| 5660 | } | ||
| 5661 | |||
| 5662 | xEmbeddedUI.SetAttributeValue("SourceFile", row.FieldAsString(4)); | ||
| 5663 | |||
| 5664 | this.UIElement.Add(xEmbeddedUI); | ||
| 5665 | foundEmbeddedUI = true; | ||
| 5666 | } | ||
| 5667 | } | ||
| 5668 | else | ||
| 5669 | { | ||
| 5670 | var xEmbeddedResource = new XElement(Names.EmbeddedUIResourceElement, | ||
| 5671 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 5672 | new XAttribute("Name", row.FieldAsString(1)), | ||
| 5673 | new XAttribute("SourceFile", row.FieldAsString(4))); | ||
| 5674 | |||
| 5675 | xEmbeddedUI.Add(xEmbeddedResource); | ||
| 5676 | foundEmbeddedResources = true; | ||
| 5677 | } | ||
| 5678 | } | ||
| 5679 | |||
| 5680 | if (!foundEmbeddedUI && foundEmbeddedResources) | ||
| 5681 | { | ||
| 5682 | // TODO: warn | ||
| 5683 | } | ||
| 5684 | } | ||
| 5685 | |||
| 5686 | /// <summary> | ||
| 5687 | /// Decompile the MsiLockPermissionsEx table. | ||
| 5688 | /// </summary> | ||
| 5689 | /// <param name="table">The table to decompile.</param> | ||
| 5690 | private void DecompileMsiLockPermissionsExTable(Table table) | ||
| 5691 | { | ||
| 5692 | foreach (var row in table.Rows) | ||
| 5693 | { | ||
| 5694 | var xPermissionEx = new XElement(Names.PermissionExElement, | ||
| 5695 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 5696 | new XAttribute("Sddl", row.FieldAsString(3)), | ||
| 5697 | XAttributeIfNotNull("Condition", row, 4)); | ||
| 5698 | |||
| 5699 | switch (row.FieldAsString(2)) | ||
| 5700 | { | ||
| 5701 | case "CreateFolder": | ||
| 5702 | case "File": | ||
| 5703 | case "Registry": | ||
| 5704 | case "ServiceInstall": | ||
| 5705 | break; | ||
| 5706 | default: | ||
| 5707 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 5708 | return; | ||
| 5709 | } | ||
| 5710 | |||
| 5711 | this.IndexElement(row, xPermissionEx); | ||
| 5712 | } | ||
| 5713 | } | ||
| 5714 | |||
| 5715 | /// <summary> | ||
| 5716 | /// Decompile the MsiPackageCertificate table. | ||
| 5717 | /// </summary> | ||
| 5718 | /// <param name="table">The table to decompile.</param> | ||
| 5719 | private void DecompileMsiPackageCertificateTable(Table table) | ||
| 5720 | { | ||
| 5721 | if (0 < table.Rows.Count) | ||
| 5722 | { | ||
| 5723 | var xPackageCertificates = new XElement(Names.PatchCertificatesElement); | ||
| 5724 | this.RootElement.Add(xPackageCertificates); | ||
| 5725 | this.AddCertificates(table, xPackageCertificates); | ||
| 5726 | } | ||
| 5727 | } | ||
| 5728 | |||
| 5729 | /// <summary> | ||
| 5730 | /// Decompile the MsiPatchCertificate table. | ||
| 5731 | /// </summary> | ||
| 5732 | /// <param name="table">The table to decompile.</param> | ||
| 5733 | private void DecompileMsiPatchCertificateTable(Table table) | ||
| 5734 | { | ||
| 5735 | if (0 < table.Rows.Count) | ||
| 5736 | { | ||
| 5737 | var xPatchCertificates = new XElement(Names.PatchCertificatesElement); | ||
| 5738 | this.RootElement.Add(xPatchCertificates); | ||
| 5739 | this.AddCertificates(table, xPatchCertificates); | ||
| 5740 | } | ||
| 5741 | } | ||
| 5742 | |||
| 5743 | /// <summary> | ||
| 5744 | /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table. | ||
| 5745 | /// </summary> | ||
| 5746 | /// <param name="table">The table being decompiled.</param> | ||
| 5747 | /// <param name="parent">DigitalCertificate parent</param> | ||
| 5748 | private void AddCertificates(Table table, XElement parent) | ||
| 5749 | { | ||
| 5750 | foreach (var row in table.Rows) | ||
| 5751 | { | ||
| 5752 | if (this.TryGetIndexedElement("MsiDigitalCertificate", out var xDigitalCertificate, row.FieldAsString(1))) | ||
| 5753 | { | ||
| 5754 | parent.Add(xDigitalCertificate); | ||
| 5755 | } | ||
| 5756 | else | ||
| 5757 | { | ||
| 5758 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", row.FieldAsString(1), "MsiDigitalCertificate")); | ||
| 5759 | } | ||
| 5760 | } | ||
| 5761 | } | ||
| 5762 | |||
| 5763 | /// <summary> | ||
| 5764 | /// Decompile the MsiShortcutProperty table. | ||
| 5765 | /// </summary> | ||
| 5766 | /// <param name="table">The table to decompile.</param> | ||
| 5767 | private void DecompileMsiShortcutPropertyTable(Table table) | ||
| 5768 | { | ||
| 5769 | foreach (var row in table.Rows) | ||
| 5770 | { | ||
| 5771 | var xProperty = new XElement(Names.ShortcutPropertyElement, | ||
| 5772 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 5773 | new XAttribute("Key", row.FieldAsString(2)), | ||
| 5774 | new XAttribute("Value", row.FieldAsString(3))); | ||
| 5775 | |||
| 5776 | this.AddChildToParent("Shortcut", xProperty, row, 1); | ||
| 5777 | } | ||
| 5778 | } | ||
| 5779 | |||
| 5780 | /// <summary> | ||
| 5781 | /// Decompile the ODBCAttribute table. | ||
| 5782 | /// </summary> | ||
| 5783 | /// <param name="table">The table to decompile.</param> | ||
| 5784 | private void DecompileODBCAttributeTable(Table table) | ||
| 5785 | { | ||
| 5786 | foreach (var row in table.Rows) | ||
| 5787 | { | ||
| 5788 | var xProperty = new XElement(Names.PropertyElement, | ||
| 5789 | new XAttribute("Id", row.FieldAsString(1)), | ||
| 5790 | row.IsColumnNull(2) ? null : new XAttribute("Value", row.FieldAsString(2))); | ||
| 5791 | |||
| 5792 | this.AddChildToParent("ODBCDriver", xProperty, row, 0); | ||
| 5793 | } | ||
| 5794 | } | ||
| 5795 | |||
| 5796 | /// <summary> | ||
| 5797 | /// Decompile the ODBCDataSource table. | ||
| 5798 | /// </summary> | ||
| 5799 | /// <param name="table">The table to decompile.</param> | ||
| 5800 | private void DecompileODBCDataSourceTable(Table table) | ||
| 5801 | { | ||
| 5802 | foreach (var row in table.Rows) | ||
| 5803 | { | ||
| 5804 | var xOdbcDataSource = new XElement(Names.ODBCDataSourceElement, | ||
| 5805 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 5806 | new XAttribute("Name", row.FieldAsString(2)), | ||
| 5807 | new XAttribute("DriverName", row.FieldAsString(3))); | ||
| 5808 | |||
| 5809 | switch (row.FieldAsInteger(4)) | ||
| 5810 | { | ||
| 5811 | case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerMachine: | ||
| 5812 | xOdbcDataSource.SetAttributeValue("Registration", "machine"); | ||
| 5813 | break; | ||
| 5814 | case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerUser: | ||
| 5815 | xOdbcDataSource.SetAttributeValue("Registration", "user"); | ||
| 5816 | break; | ||
| 5817 | default: | ||
| 5818 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
| 5819 | break; | ||
| 5820 | } | ||
| 5821 | |||
| 5822 | this.IndexElement(row, xOdbcDataSource); | ||
| 5823 | } | ||
| 5824 | } | ||
| 5825 | |||
| 5826 | /// <summary> | ||
| 5827 | /// Decompile the ODBCDriver table. | ||
| 5828 | /// </summary> | ||
| 5829 | /// <param name="table">The table to decompile.</param> | ||
| 5830 | private void DecompileODBCDriverTable(Table table) | ||
| 5831 | { | ||
| 5832 | foreach (var row in table.Rows) | ||
| 5833 | { | ||
| 5834 | var xOdbcDriver = new XElement(Names.ODBCDriverElement, | ||
| 5835 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 5836 | new XAttribute("Name", row.FieldAsString(2)), | ||
| 5837 | new XAttribute("File", row.FieldAsString(3)), | ||
| 5838 | XAttributeIfNotNull("SetupFile", row, 4)); | ||
| 5839 | |||
| 5840 | this.AddChildToParent("Component", xOdbcDriver, row, 1); | ||
| 5841 | this.IndexElement(row, xOdbcDriver); | ||
| 5842 | } | ||
| 5843 | } | ||
| 5844 | |||
| 5845 | /// <summary> | ||
| 5846 | /// Decompile the ODBCSourceAttribute table. | ||
| 5847 | /// </summary> | ||
| 5848 | /// <param name="table">The table to decompile.</param> | ||
| 5849 | private void DecompileODBCSourceAttributeTable(Table table) | ||
| 5850 | { | ||
| 5851 | foreach (var row in table.Rows) | ||
| 5852 | { | ||
| 5853 | var xProperty = new XElement(Names.PropertyElement, | ||
| 5854 | new XAttribute("Id", row.FieldAsString(1)), | ||
| 5855 | XAttributeIfNotNull("Value", row, 2)); | ||
| 5856 | |||
| 5857 | this.AddChildToParent("ODBCDataSource", xProperty, row, 0); | ||
| 5858 | } | ||
| 5859 | } | ||
| 5860 | |||
| 5861 | /// <summary> | ||
| 5862 | /// Decompile the ODBCTranslator table. | ||
| 5863 | /// </summary> | ||
| 5864 | /// <param name="table">The table to decompile.</param> | ||
| 5865 | private void DecompileODBCTranslatorTable(Table table) | ||
| 5866 | { | ||
| 5867 | foreach (var row in table.Rows) | ||
| 5868 | { | ||
| 5869 | var xOdbcTranslator = new XElement(Names.ODBCTranslatorElement, | ||
| 5870 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 5871 | new XAttribute("Name", row.FieldAsString(2)), | ||
| 5872 | new XAttribute("File", row.FieldAsString(3)), | ||
| 5873 | XAttributeIfNotNull("SetupFile", row, 4)); | ||
| 5874 | |||
| 5875 | this.AddChildToParent("Component", xOdbcTranslator, row, 1); | ||
| 5876 | } | ||
| 5877 | } | ||
| 5878 | |||
| 5879 | /// <summary> | ||
| 5880 | /// Decompile the PatchMetadata table. | ||
| 5881 | /// </summary> | ||
| 5882 | /// <param name="table">The table to decompile.</param> | ||
| 5883 | private void DecompilePatchMetadataTable(Table table) | ||
| 5884 | { | ||
| 5885 | if (0 < table.Rows.Count) | ||
| 5886 | { | ||
| 5887 | var xPatchMetadata = new XElement(Names.PatchMetadataElement); | ||
| 5888 | |||
| 5889 | foreach (var row in table.Rows) | ||
| 5890 | { | ||
| 5891 | var value = row.FieldAsString(2); | ||
| 5892 | |||
| 5893 | switch (row.FieldAsString(1)) | ||
| 5894 | { | ||
| 5895 | case "AllowRemoval": | ||
| 5896 | if ("1" == value) | ||
| 5897 | { | ||
| 5898 | xPatchMetadata.SetAttributeValue("AllowRemoval", "yes"); | ||
| 5899 | } | ||
| 5900 | break; | ||
| 5901 | case "Classification": | ||
| 5902 | if (null != value) | ||
| 5903 | { | ||
| 5904 | xPatchMetadata.SetAttributeValue("Classification", value); | ||
| 5905 | } | ||
| 5906 | break; | ||
| 5907 | case "CreationTimeUTC": | ||
| 5908 | if (null != value) | ||
| 5909 | { | ||
| 5910 | xPatchMetadata.SetAttributeValue("CreationTimeUTC", value); | ||
| 5911 | } | ||
| 5912 | break; | ||
| 5913 | case "Description": | ||
| 5914 | if (null != value) | ||
| 5915 | { | ||
| 5916 | xPatchMetadata.SetAttributeValue("Description", value); | ||
| 5917 | } | ||
| 5918 | break; | ||
| 5919 | case "DisplayName": | ||
| 5920 | if (null != value) | ||
| 5921 | { | ||
| 5922 | xPatchMetadata.SetAttributeValue("DisplayName", value); | ||
| 5923 | } | ||
| 5924 | break; | ||
| 5925 | case "ManufacturerName": | ||
| 5926 | if (null != value) | ||
| 5927 | { | ||
| 5928 | xPatchMetadata.SetAttributeValue("ManufacturerName", value); | ||
| 5929 | } | ||
| 5930 | break; | ||
| 5931 | case "MinorUpdateTargetRTM": | ||
| 5932 | if (null != value) | ||
| 5933 | { | ||
| 5934 | xPatchMetadata.SetAttributeValue("MinorUpdateTargetRTM", value); | ||
| 5935 | } | ||
| 5936 | break; | ||
| 5937 | case "MoreInfoURL": | ||
| 5938 | if (null != value) | ||
| 5939 | { | ||
| 5940 | xPatchMetadata.SetAttributeValue("MoreInfoURL", value); | ||
| 5941 | } | ||
| 5942 | break; | ||
| 5943 | case "OptimizeCA": | ||
| 5944 | var xOptimizeCustomActions = new XElement(Names.OptimizeCustomActionsElement); | ||
| 5945 | var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); | ||
| 5946 | if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipAssignment) & optimizeCA)) | ||
| 5947 | { | ||
| 5948 | xOptimizeCustomActions.SetAttributeValue("SkipAssignment", "yes"); | ||
| 5949 | } | ||
| 5950 | |||
| 5951 | if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipImmediate) & optimizeCA)) | ||
| 5952 | { | ||
| 5953 | xOptimizeCustomActions.SetAttributeValue("SkipImmediate", "yes"); | ||
| 5954 | } | ||
| 5955 | |||
| 5956 | if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipDeferred) & optimizeCA)) | ||
| 5957 | { | ||
| 5958 | xOptimizeCustomActions.SetAttributeValue("SkipDeferred", "yes"); | ||
| 5959 | } | ||
| 5960 | |||
| 5961 | xPatchMetadata.Add(xOptimizeCustomActions); | ||
| 5962 | break; | ||
| 5963 | case "OptimizedInstallMode": | ||
| 5964 | if ("1" == value) | ||
| 5965 | { | ||
| 5966 | xPatchMetadata.SetAttributeValue("OptimizedInstallMode", "yes"); | ||
| 5967 | } | ||
| 5968 | break; | ||
| 5969 | case "TargetProductName": | ||
| 5970 | if (null != value) | ||
| 5971 | { | ||
| 5972 | xPatchMetadata.SetAttributeValue("TargetProductName", value); | ||
| 5973 | } | ||
| 5974 | break; | ||
| 5975 | default: | ||
| 5976 | var xCustomProperty = new XElement(Names.CustomPropertyElement, | ||
| 5977 | XAttributeIfNotNull("Company", row, 0), | ||
| 5978 | XAttributeIfNotNull("Property", row, 1), | ||
| 5979 | XAttributeIfNotNull("Value", row, 2)); | ||
| 5980 | |||
| 5981 | xPatchMetadata.Add(xCustomProperty); | ||
| 5982 | break; | ||
| 5983 | } | ||
| 5984 | } | ||
| 5985 | |||
| 5986 | this.RootElement.Add(xPatchMetadata); | ||
| 5987 | } | ||
| 5988 | } | ||
| 5989 | |||
| 5990 | /// <summary> | ||
| 5991 | /// Decompile the PatchSequence table. | ||
| 5992 | /// </summary> | ||
| 5993 | /// <param name="table">The table to decompile.</param> | ||
| 5994 | private void DecompilePatchSequenceTable(Table table) | ||
| 5995 | { | ||
| 5996 | foreach (var row in table.Rows) | ||
| 5997 | { | ||
| 5998 | var patchSequence = new XElement(Names.PatchSequenceElement, | ||
| 5999 | new XAttribute("PatchFamily", row.FieldAsString(0))); | ||
| 6000 | |||
| 6001 | if (!row.IsColumnNull(1)) | ||
| 6002 | { | ||
| 6003 | try | ||
| 6004 | { | ||
| 6005 | var guid = new Guid(row.FieldAsString(1)); | ||
| 6006 | |||
| 6007 | patchSequence.SetAttributeValue("ProductCode", row.FieldAsString(1)); | ||
| 6008 | } | ||
| 6009 | catch // non-guid value | ||
| 6010 | { | ||
| 6011 | patchSequence.SetAttributeValue("TargetImage", row.FieldAsString(1)); | ||
| 6012 | } | ||
| 6013 | } | ||
| 6014 | |||
| 6015 | if (!row.IsColumnNull(2)) | ||
| 6016 | { | ||
| 6017 | patchSequence.SetAttributeValue("Sequence", row.FieldAsString(2)); | ||
| 6018 | } | ||
| 6019 | |||
| 6020 | if (!row.IsColumnNull(3) && 0x1 == row.FieldAsInteger(3)) | ||
| 6021 | { | ||
| 6022 | patchSequence.SetAttributeValue("Supersede", "yes"); | ||
| 6023 | } | ||
| 6024 | |||
| 6025 | this.RootElement.Add(patchSequence); | ||
| 6026 | } | ||
| 6027 | } | ||
| 6028 | |||
| 6029 | /// <summary> | ||
| 6030 | /// Decompile the ProgId table. | ||
| 6031 | /// </summary> | ||
| 6032 | /// <param name="table">The table to decompile.</param> | ||
| 6033 | private void DecompileProgIdTable(Table table) | ||
| 6034 | { | ||
| 6035 | foreach (var row in table.Rows) | ||
| 6036 | { | ||
| 6037 | var xProgId = new XElement(Names.ProgIdElement, | ||
| 6038 | new XAttribute("Advertise", "yes"), | ||
| 6039 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6040 | XAttributeIfNotNull("Description", row, 3), | ||
| 6041 | XAttributeIfNotNull("Icon", row, 4), | ||
| 6042 | XAttributeIfNotNull("IconIndex", row, 5)); | ||
| 6043 | |||
| 6044 | this.IndexElement(row, xProgId); | ||
| 6045 | } | ||
| 6046 | |||
| 6047 | // nest the ProgIds | ||
| 6048 | foreach (var row in table.Rows) | ||
| 6049 | { | ||
| 6050 | var xProgId = this.GetIndexedElement(row); | ||
| 6051 | |||
| 6052 | if (!row.IsColumnNull(1)) | ||
| 6053 | { | ||
| 6054 | this.AddChildToParent("ProgId", xProgId, row, 1); | ||
| 6055 | } | ||
| 6056 | else if (!row.IsColumnNull(2)) | ||
| 6057 | { | ||
| 6058 | // nesting is handled in FinalizeProgIdTable | ||
| 6059 | } | ||
| 6060 | else | ||
| 6061 | { | ||
| 6062 | // TODO: warn for orphaned ProgId | ||
| 6063 | } | ||
| 6064 | } | ||
| 6065 | } | ||
| 6066 | |||
| 6067 | /// <summary> | ||
| 6068 | /// Decompile the Properties table. | ||
| 6069 | /// </summary> | ||
| 6070 | /// <param name="table">The table to decompile.</param> | ||
| 6071 | private void DecompilePropertiesTable(Table table) | ||
| 6072 | { | ||
| 6073 | foreach (var row in table.Rows) | ||
| 6074 | { | ||
| 6075 | var name = row.FieldAsString(0); | ||
| 6076 | var value = row.FieldAsString(1); | ||
| 6077 | |||
| 6078 | switch (name) | ||
| 6079 | { | ||
| 6080 | case "AllowProductCodeMismatches": | ||
| 6081 | if ("1" == value) | ||
| 6082 | { | ||
| 6083 | this.RootElement.SetAttributeValue("AllowProductCodeMismatches", "yes"); | ||
| 6084 | } | ||
| 6085 | break; | ||
| 6086 | case "AllowProductVersionMajorMismatches": | ||
| 6087 | if ("1" == value) | ||
| 6088 | { | ||
| 6089 | this.RootElement.SetAttributeValue("AllowMajorVersionMismatches", "yes"); | ||
| 6090 | } | ||
| 6091 | break; | ||
| 6092 | case "ApiPatchingSymbolFlags": | ||
| 6093 | if (null != value) | ||
| 6094 | { | ||
| 6095 | try | ||
| 6096 | { | ||
| 6097 | // remove the leading "0x" if its present | ||
| 6098 | if (value.StartsWith("0x", StringComparison.Ordinal)) | ||
| 6099 | { | ||
| 6100 | value = value.Substring(2); | ||
| 6101 | } | ||
| 6102 | |||
| 6103 | this.RootElement.SetAttributeValue("SymbolFlags", Convert.ToInt32(value, 16)); | ||
| 6104 | } | ||
| 6105 | catch | ||
| 6106 | { | ||
| 6107 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 6108 | } | ||
| 6109 | } | ||
| 6110 | break; | ||
| 6111 | case "DontRemoveTempFolderWhenFinished": | ||
| 6112 | if ("1" == value) | ||
| 6113 | { | ||
| 6114 | this.RootElement.SetAttributeValue("CleanWorkingFolder", "no"); | ||
| 6115 | } | ||
| 6116 | break; | ||
| 6117 | case "IncludeWholeFilesOnly": | ||
| 6118 | if ("1" == value) | ||
| 6119 | { | ||
| 6120 | this.RootElement.SetAttributeValue("WholeFilesOnly", "yes"); | ||
| 6121 | } | ||
| 6122 | break; | ||
| 6123 | case "ListOfPatchGUIDsToReplace": | ||
| 6124 | if (null != value) | ||
| 6125 | { | ||
| 6126 | var guidRegex = new Regex(@"\{[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}"); | ||
| 6127 | var guidMatches = guidRegex.Matches(value); | ||
| 6128 | |||
| 6129 | foreach (Match guidMatch in guidMatches) | ||
| 6130 | { | ||
| 6131 | var xReplacePatch = new XElement(Names.ReplacePatchElement, | ||
| 6132 | new XAttribute("Id", guidMatch.Value)); | ||
| 6133 | |||
| 6134 | this.RootElement.Add(xReplacePatch); | ||
| 6135 | } | ||
| 6136 | } | ||
| 6137 | break; | ||
| 6138 | case "ListOfTargetProductCodes": | ||
| 6139 | if (null != value) | ||
| 6140 | { | ||
| 6141 | var targetProductCodes = value.Split(';'); | ||
| 6142 | |||
| 6143 | foreach (var targetProductCodeString in targetProductCodes) | ||
| 6144 | { | ||
| 6145 | var xTargetProductCode = new XElement(Names.TargetProductCodeElement, | ||
| 6146 | new XAttribute("Id", targetProductCodeString)); | ||
| 6147 | |||
| 6148 | this.RootElement.Add(xTargetProductCode); | ||
| 6149 | } | ||
| 6150 | } | ||
| 6151 | break; | ||
| 6152 | case "PatchGUID": | ||
| 6153 | this.RootElement.SetAttributeValue("Id", value); | ||
| 6154 | break; | ||
| 6155 | case "PatchSourceList": | ||
| 6156 | this.RootElement.SetAttributeValue("SourceList", value); | ||
| 6157 | break; | ||
| 6158 | case "PatchOutputPath": | ||
| 6159 | this.RootElement.SetAttributeValue("OutputPath", value); | ||
| 6160 | break; | ||
| 6161 | default: | ||
| 6162 | var patchProperty = new XElement(Names.PatchPropertyElement, | ||
| 6163 | new XAttribute("Name", name), | ||
| 6164 | new XAttribute("Value", value)); | ||
| 6165 | |||
| 6166 | this.RootElement.Add(patchProperty); | ||
| 6167 | break; | ||
| 6168 | } | ||
| 6169 | } | ||
| 6170 | } | ||
| 6171 | |||
| 6172 | /// <summary> | ||
| 6173 | /// Decompile the Property table. | ||
| 6174 | /// </summary> | ||
| 6175 | /// <param name="table">The table to decompile.</param> | ||
| 6176 | private void DecompilePropertyTable(Table table) | ||
| 6177 | { | ||
| 6178 | foreach (var row in table.Rows) | ||
| 6179 | { | ||
| 6180 | var id = row.FieldAsString(0); | ||
| 6181 | var value = row.FieldAsString(1); | ||
| 6182 | |||
| 6183 | if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id) | ||
| 6184 | { | ||
| 6185 | if (0 < value.Length) | ||
| 6186 | { | ||
| 6187 | foreach (var propertyId in value.Split(';')) | ||
| 6188 | { | ||
| 6189 | if (WixUpgradeConstants.DowngradeDetectedProperty == propertyId || WixUpgradeConstants.UpgradeDetectedProperty == propertyId) | ||
| 6190 | { | ||
| 6191 | continue; | ||
| 6192 | } | ||
| 6193 | |||
| 6194 | var property = propertyId; | ||
| 6195 | var suppressModulularization = false; | ||
| 6196 | if (OutputType.Module == this.OutputType) | ||
| 6197 | { | ||
| 6198 | if (propertyId.EndsWith(this.ModularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) | ||
| 6199 | { | ||
| 6200 | property = propertyId.Substring(0, propertyId.Length - this.ModularizationGuid.Length + 1); | ||
| 6201 | } | ||
| 6202 | else | ||
| 6203 | { | ||
| 6204 | suppressModulularization = true; | ||
| 6205 | } | ||
| 6206 | } | ||
| 6207 | |||
| 6208 | var xSpecialProperty = this.EnsureProperty(property); | ||
| 6209 | if (suppressModulularization) | ||
| 6210 | { | ||
| 6211 | xSpecialProperty.SetAttributeValue("SuppressModularization", "yes"); | ||
| 6212 | } | ||
| 6213 | |||
| 6214 | switch (id) | ||
| 6215 | { | ||
| 6216 | case "AdminProperties": | ||
| 6217 | xSpecialProperty.SetAttributeValue("Admin", "yes"); | ||
| 6218 | break; | ||
| 6219 | case "MsiHiddenProperties": | ||
| 6220 | xSpecialProperty.SetAttributeValue("Hidden", "yes"); | ||
| 6221 | break; | ||
| 6222 | case "SecureCustomProperties": | ||
| 6223 | xSpecialProperty.SetAttributeValue("Secure", "yes"); | ||
| 6224 | break; | ||
| 6225 | } | ||
| 6226 | } | ||
| 6227 | } | ||
| 6228 | |||
| 6229 | continue; | ||
| 6230 | } | ||
| 6231 | else if (OutputType.Product == this.OutputType) | ||
| 6232 | { | ||
| 6233 | switch (id) | ||
| 6234 | { | ||
| 6235 | case "Manufacturer": | ||
| 6236 | this.RootElement.SetAttributeValue("Manufacturer", value); | ||
| 6237 | continue; | ||
| 6238 | case "ProductCode": | ||
| 6239 | this.RootElement.SetAttributeValue("ProductCode", value.ToUpper(CultureInfo.InvariantCulture)); | ||
| 6240 | continue; | ||
| 6241 | case "ProductLanguage": | ||
| 6242 | this.RootElement.SetAttributeValue("Language", value); | ||
| 6243 | continue; | ||
| 6244 | case "ProductName": | ||
| 6245 | this.RootElement.SetAttributeValue("Name", value); | ||
| 6246 | continue; | ||
| 6247 | case "ProductVersion": | ||
| 6248 | this.RootElement.SetAttributeValue("Version", value); | ||
| 6249 | continue; | ||
| 6250 | case "UpgradeCode": | ||
| 6251 | this.RootElement.SetAttributeValue("UpgradeCode", value); | ||
| 6252 | continue; | ||
| 6253 | } | ||
| 6254 | } | ||
| 6255 | |||
| 6256 | if (!this.SuppressUI || "ErrorDialog" != id) | ||
| 6257 | { | ||
| 6258 | var xProperty = this.EnsureProperty(id); | ||
| 6259 | |||
| 6260 | xProperty.SetAttributeValue("Value", value); | ||
| 6261 | } | ||
| 6262 | } | ||
| 6263 | } | ||
| 6264 | |||
| 6265 | /// <summary> | ||
| 6266 | /// Decompile the PublishComponent table. | ||
| 6267 | /// </summary> | ||
| 6268 | /// <param name="table">The table to decompile.</param> | ||
| 6269 | private void DecompilePublishComponentTable(Table table) | ||
| 6270 | { | ||
| 6271 | foreach (var row in table.Rows) | ||
| 6272 | { | ||
| 6273 | var category = new XElement(Names.CategoryElement, | ||
| 6274 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6275 | new XAttribute("Qualifier", row.FieldAsString(1)), | ||
| 6276 | XAttributeIfNotNull("AppData", row, 3)); | ||
| 6277 | |||
| 6278 | this.AddChildToParent("Component", category, row, 2); | ||
| 6279 | } | ||
| 6280 | } | ||
| 6281 | |||
| 6282 | /// <summary> | ||
| 6283 | /// Decompile the RadioButton table. | ||
| 6284 | /// </summary> | ||
| 6285 | /// <param name="table">The table to decompile.</param> | ||
| 6286 | private void DecompileRadioButtonTable(Table table) | ||
| 6287 | { | ||
| 6288 | foreach (var row in table.Rows) | ||
| 6289 | { | ||
| 6290 | var radioButton = new XElement(Names.RadioButtonElement, | ||
| 6291 | new XAttribute("Value", row.FieldAsString(2)), | ||
| 6292 | new XAttribute("X", row.FieldAsInteger(3)), | ||
| 6293 | new XAttribute("Y", row.FieldAsInteger(4)), | ||
| 6294 | new XAttribute("Width", row.FieldAsInteger(5)), | ||
| 6295 | new XAttribute("Height", row.FieldAsInteger(6)), | ||
| 6296 | XAttributeIfNotNull("Text", row, 7)); | ||
| 6297 | |||
| 6298 | if (!row.IsColumnNull(8)) | ||
| 6299 | { | ||
| 6300 | var help = (row.FieldAsString(8)).Split('|'); | ||
| 6301 | |||
| 6302 | if (2 == help.Length) | ||
| 6303 | { | ||
| 6304 | if (0 < help[0].Length) | ||
| 6305 | { | ||
| 6306 | radioButton.SetAttributeValue("ToolTip", help[0]); | ||
| 6307 | } | ||
| 6308 | |||
| 6309 | if (0 < help[1].Length) | ||
| 6310 | { | ||
| 6311 | radioButton.SetAttributeValue("Help", help[1]); | ||
| 6312 | } | ||
| 6313 | } | ||
| 6314 | } | ||
| 6315 | |||
| 6316 | this.IndexElement(row, radioButton); | ||
| 6317 | } | ||
| 6318 | |||
| 6319 | // nest the radio buttons | ||
| 6320 | var xRadioButtonGroups = new Dictionary<string, XElement>(); | ||
| 6321 | foreach (var row in table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1))) | ||
| 6322 | { | ||
| 6323 | var xRadioButton = this.GetIndexedElement(row); | ||
| 6324 | |||
| 6325 | if (!xRadioButtonGroups.TryGetValue(row.FieldAsString(0), out var xRadioButtonGroup)) | ||
| 6326 | { | ||
| 6327 | xRadioButtonGroup = new XElement(Names.RadioButtonGroupElement, | ||
| 6328 | new XAttribute("Property", row.FieldAsString(0))); | ||
| 6329 | |||
| 6330 | this.UIElement.Add(xRadioButtonGroup); | ||
| 6331 | xRadioButtonGroups.Add(row.FieldAsString(0), xRadioButtonGroup); | ||
| 6332 | } | ||
| 6333 | |||
| 6334 | xRadioButtonGroup.Add(xRadioButton); | ||
| 6335 | } | ||
| 6336 | } | ||
| 6337 | |||
| 6338 | /// <summary> | ||
| 6339 | /// Decompile the Registry table. | ||
| 6340 | /// </summary> | ||
| 6341 | /// <param name="table">The table to decompile.</param> | ||
| 6342 | private void DecompileRegistryTable(Table table) | ||
| 6343 | { | ||
| 6344 | foreach (var row in table.Rows) | ||
| 6345 | { | ||
| 6346 | if (("-" == row.FieldAsString(3) || "+" == row.FieldAsString(3) || "*" == row.FieldAsString(3)) && row.IsColumnNull(4)) | ||
| 6347 | { | ||
| 6348 | var xRegistryKey = new XElement(Names.RegistryKeyElement, | ||
| 6349 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6350 | new XAttribute("Key", row.FieldAsString(2))); | ||
| 6351 | |||
| 6352 | if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) | ||
| 6353 | { | ||
| 6354 | xRegistryKey.SetAttributeValue("Root", registryRootType); | ||
| 6355 | } | ||
| 6356 | |||
| 6357 | switch (row.FieldAsString(3)) | ||
| 6358 | { | ||
| 6359 | case "+": | ||
| 6360 | xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); | ||
| 6361 | break; | ||
| 6362 | case "-": | ||
| 6363 | xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); | ||
| 6364 | break; | ||
| 6365 | case "*": | ||
| 6366 | xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); | ||
| 6367 | xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); | ||
| 6368 | break; | ||
| 6369 | } | ||
| 6370 | |||
| 6371 | this.IndexElement(row, xRegistryKey); | ||
| 6372 | } | ||
| 6373 | else | ||
| 6374 | { | ||
| 6375 | var xRegistryValue = new XElement(Names.RegistryValueElement, | ||
| 6376 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6377 | new XAttribute("Key", row.FieldAsString(2)), | ||
| 6378 | XAttributeIfNotNull("Name", row, 3)); | ||
| 6379 | |||
| 6380 | if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) | ||
| 6381 | { | ||
| 6382 | xRegistryValue.SetAttributeValue("Root", registryRootType); | ||
| 6383 | } | ||
| 6384 | |||
| 6385 | if (!row.IsColumnNull(4)) | ||
| 6386 | { | ||
| 6387 | var value = row.FieldAsString(4); | ||
| 6388 | |||
| 6389 | if (value.StartsWith("#x", StringComparison.Ordinal)) | ||
| 6390 | { | ||
| 6391 | xRegistryValue.SetAttributeValue("Type", "binary"); | ||
| 6392 | xRegistryValue.SetAttributeValue("Value", value.Substring(2)); | ||
| 6393 | } | ||
| 6394 | else if (value.StartsWith("#%", StringComparison.Ordinal)) | ||
| 6395 | { | ||
| 6396 | xRegistryValue.SetAttributeValue("Type", "expandable"); | ||
| 6397 | xRegistryValue.SetAttributeValue("Value", value.Substring(2)); | ||
| 6398 | } | ||
| 6399 | else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal)) | ||
| 6400 | { | ||
| 6401 | xRegistryValue.SetAttributeValue("Type", "integer"); | ||
| 6402 | xRegistryValue.SetAttributeValue("Value", value.Substring(1)); | ||
| 6403 | } | ||
| 6404 | else | ||
| 6405 | { | ||
| 6406 | if (value.StartsWith("##", StringComparison.Ordinal)) | ||
| 6407 | { | ||
| 6408 | value = value.Substring(1); | ||
| 6409 | } | ||
| 6410 | |||
| 6411 | if (0 <= value.IndexOf("[~]", StringComparison.Ordinal)) | ||
| 6412 | { | ||
| 6413 | xRegistryValue.SetAttributeValue("Type", "multiString"); | ||
| 6414 | |||
| 6415 | if ("[~]" == value) | ||
| 6416 | { | ||
| 6417 | value = String.Empty; | ||
| 6418 | } | ||
| 6419 | else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal)) | ||
| 6420 | { | ||
| 6421 | value = value.Substring(3, value.Length - 6); | ||
| 6422 | } | ||
| 6423 | else if (value.StartsWith("[~]", StringComparison.Ordinal)) | ||
| 6424 | { | ||
| 6425 | xRegistryValue.SetAttributeValue("Action", "append"); | ||
| 6426 | value = value.Substring(3); | ||
| 6427 | } | ||
| 6428 | else if (value.EndsWith("[~]", StringComparison.Ordinal)) | ||
| 6429 | { | ||
| 6430 | xRegistryValue.SetAttributeValue("Action", "prepend"); | ||
| 6431 | value = value.Substring(0, value.Length - 3); | ||
| 6432 | } | ||
| 6433 | |||
| 6434 | var multiValues = NullSplitter.Split(value); | ||
| 6435 | foreach (var multiValue in multiValues) | ||
| 6436 | { | ||
| 6437 | var xMultiStringValue = new XElement(Names.MultiStringElement, | ||
| 6438 | new XAttribute("Value", multiValue)); | ||
| 6439 | |||
| 6440 | xRegistryValue.Add(xMultiStringValue); | ||
| 6441 | } | ||
| 6442 | } | ||
| 6443 | else | ||
| 6444 | { | ||
| 6445 | xRegistryValue.SetAttributeValue("Type", "string"); | ||
| 6446 | xRegistryValue.SetAttributeValue("Value", value); | ||
| 6447 | } | ||
| 6448 | } | ||
| 6449 | } | ||
| 6450 | else | ||
| 6451 | { | ||
| 6452 | xRegistryValue.SetAttributeValue("Type", "string"); | ||
| 6453 | xRegistryValue.SetAttributeValue("Value", String.Empty); | ||
| 6454 | } | ||
| 6455 | |||
| 6456 | this.IndexElement(row, xRegistryValue); | ||
| 6457 | } | ||
| 6458 | } | ||
| 6459 | } | ||
| 6460 | |||
| 6461 | /// <summary> | ||
| 6462 | /// Decompile the RegLocator table. | ||
| 6463 | /// </summary> | ||
| 6464 | /// <param name="table">The table to decompile.</param> | ||
| 6465 | private void DecompileRegLocatorTable(Table table) | ||
| 6466 | { | ||
| 6467 | foreach (var row in table.Rows) | ||
| 6468 | { | ||
| 6469 | var xRegistrySearch = new XElement(Names.RegistrySearchElement, | ||
| 6470 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6471 | new XAttribute("Key", row.FieldAsString(2)), | ||
| 6472 | XAttributeIfNotNull("Name", row, 3)); | ||
| 6473 | |||
| 6474 | switch (row.FieldAsInteger(1)) | ||
| 6475 | { | ||
| 6476 | case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: | ||
| 6477 | xRegistrySearch.SetAttributeValue("Root", "HKCR"); | ||
| 6478 | break; | ||
| 6479 | case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: | ||
| 6480 | xRegistrySearch.SetAttributeValue("Root", "HKCU"); | ||
| 6481 | break; | ||
| 6482 | case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: | ||
| 6483 | xRegistrySearch.SetAttributeValue("Root", "HKLM"); | ||
| 6484 | break; | ||
| 6485 | case WindowsInstallerConstants.MsidbRegistryRootUsers: | ||
| 6486 | xRegistrySearch.SetAttributeValue("Root", "HKU"); | ||
| 6487 | break; | ||
| 6488 | default: | ||
| 6489 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); | ||
| 6490 | break; | ||
| 6491 | } | ||
| 6492 | |||
| 6493 | if (row.IsColumnNull(4)) | ||
| 6494 | { | ||
| 6495 | xRegistrySearch.SetAttributeValue("Type", "file"); | ||
| 6496 | } | ||
| 6497 | else | ||
| 6498 | { | ||
| 6499 | var type = row.FieldAsInteger(4); | ||
| 6500 | |||
| 6501 | if (WindowsInstallerConstants.MsidbLocatorType64bit == (type & WindowsInstallerConstants.MsidbLocatorType64bit)) | ||
| 6502 | { | ||
| 6503 | xRegistrySearch.SetAttributeValue("Bitness", "always64"); | ||
| 6504 | type &= ~WindowsInstallerConstants.MsidbLocatorType64bit; | ||
| 6505 | } | ||
| 6506 | else | ||
| 6507 | { | ||
| 6508 | xRegistrySearch.SetAttributeValue("Bitness", "always32"); | ||
| 6509 | } | ||
| 6510 | |||
| 6511 | switch (type) | ||
| 6512 | { | ||
| 6513 | case WindowsInstallerConstants.MsidbLocatorTypeDirectory: | ||
| 6514 | xRegistrySearch.SetAttributeValue("Type", "directory"); | ||
| 6515 | break; | ||
| 6516 | case WindowsInstallerConstants.MsidbLocatorTypeFileName: | ||
| 6517 | xRegistrySearch.SetAttributeValue("Type", "file"); | ||
| 6518 | break; | ||
| 6519 | case WindowsInstallerConstants.MsidbLocatorTypeRawValue: | ||
| 6520 | xRegistrySearch.SetAttributeValue("Type", "raw"); | ||
| 6521 | break; | ||
| 6522 | default: | ||
| 6523 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
| 6524 | break; | ||
| 6525 | } | ||
| 6526 | } | ||
| 6527 | |||
| 6528 | this.IndexElement(row, xRegistrySearch); | ||
| 6529 | } | ||
| 6530 | } | ||
| 6531 | |||
| 6532 | /// <summary> | ||
| 6533 | /// Decompile the RemoveFile table. | ||
| 6534 | /// </summary> | ||
| 6535 | /// <param name="table">The table to decompile.</param> | ||
| 6536 | private void DecompileRemoveFileTable(Table table) | ||
| 6537 | { | ||
| 6538 | foreach (var row in table.Rows) | ||
| 6539 | { | ||
| 6540 | if (row.IsColumnNull(2)) | ||
| 6541 | { | ||
| 6542 | var xRemoveFolder = new XElement(Names.RemoveFolderElement, | ||
| 6543 | new XAttribute("Id", row.FieldAsString(0))); | ||
| 6544 | |||
| 6545 | // directory/property is set in FinalizeDecompile | ||
| 6546 | |||
| 6547 | switch (row.FieldAsInteger(4)) | ||
| 6548 | { | ||
| 6549 | case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: | ||
| 6550 | xRemoveFolder.SetAttributeValue("On", "install"); | ||
| 6551 | break; | ||
| 6552 | case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: | ||
| 6553 | xRemoveFolder.SetAttributeValue("On", "uninstall"); | ||
| 6554 | break; | ||
| 6555 | case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: | ||
| 6556 | xRemoveFolder.SetAttributeValue("On", "both"); | ||
| 6557 | break; | ||
| 6558 | default: | ||
| 6559 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
| 6560 | break; | ||
| 6561 | } | ||
| 6562 | |||
| 6563 | this.AddChildToParent("Component", xRemoveFolder, row, 1); | ||
| 6564 | this.IndexElement(row, xRemoveFolder); | ||
| 6565 | } | ||
| 6566 | else | ||
| 6567 | { | ||
| 6568 | var xRemoveFile = new XElement(Names.RemoveFileElement, | ||
| 6569 | new XAttribute("Id", row.FieldAsString(0))); | ||
| 6570 | |||
| 6571 | var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); | ||
| 6572 | if (null != names[0] && null != names[1]) | ||
| 6573 | { | ||
| 6574 | xRemoveFile.SetAttributeValue("ShortName", names[0]); | ||
| 6575 | xRemoveFile.SetAttributeValue("Name", names[1]); | ||
| 6576 | } | ||
| 6577 | else if (null != names[0]) | ||
| 6578 | { | ||
| 6579 | xRemoveFile.SetAttributeValue("Name", names[0]); | ||
| 6580 | } | ||
| 6581 | |||
| 6582 | // directory/property is set in FinalizeDecompile | ||
| 6583 | |||
| 6584 | switch (row.FieldAsInteger(4)) | ||
| 6585 | { | ||
| 6586 | case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: | ||
| 6587 | xRemoveFile.SetAttributeValue("On", "install"); | ||
| 6588 | break; | ||
| 6589 | case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: | ||
| 6590 | xRemoveFile.SetAttributeValue("On", "uninstall"); | ||
| 6591 | break; | ||
| 6592 | case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: | ||
| 6593 | xRemoveFile.SetAttributeValue("On", "both"); | ||
| 6594 | break; | ||
| 6595 | default: | ||
| 6596 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
| 6597 | break; | ||
| 6598 | } | ||
| 6599 | |||
| 6600 | this.AddChildToParent("Component", xRemoveFile, row, 1); | ||
| 6601 | this.IndexElement(row, xRemoveFile); | ||
| 6602 | } | ||
| 6603 | } | ||
| 6604 | } | ||
| 6605 | |||
| 6606 | /// <summary> | ||
| 6607 | /// Decompile the RemoveIniFile table. | ||
| 6608 | /// </summary> | ||
| 6609 | /// <param name="table">The table to decompile.</param> | ||
| 6610 | private void DecompileRemoveIniFileTable(Table table) | ||
| 6611 | { | ||
| 6612 | foreach (var row in table.Rows) | ||
| 6613 | { | ||
| 6614 | var xIniFile = new XElement(Names.IniFileElement, | ||
| 6615 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6616 | XAttributeIfNotNull("Directory", row, 2), | ||
| 6617 | new XAttribute("Section", row.FieldAsString(3)), | ||
| 6618 | new XAttribute("Key", row.FieldAsString(4)), | ||
| 6619 | XAttributeIfNotNull("Value", row, 5)); | ||
| 6620 | |||
| 6621 | var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); | ||
| 6622 | if (null != names[0] && null != names[1]) | ||
| 6623 | { | ||
| 6624 | xIniFile.SetAttributeValue("ShortName", names[0]); | ||
| 6625 | xIniFile.SetAttributeValue("Name", names[1]); | ||
| 6626 | } | ||
| 6627 | else if (null != names[0]) | ||
| 6628 | { | ||
| 6629 | xIniFile.SetAttributeValue("Name", names[0]); | ||
| 6630 | } | ||
| 6631 | |||
| 6632 | switch (row.FieldAsInteger(6)) | ||
| 6633 | { | ||
| 6634 | case WindowsInstallerConstants.MsidbIniFileActionRemoveLine: | ||
| 6635 | xIniFile.SetAttributeValue("Action", "removeLine"); | ||
| 6636 | break; | ||
| 6637 | case WindowsInstallerConstants.MsidbIniFileActionRemoveTag: | ||
| 6638 | xIniFile.SetAttributeValue("Action", "removeTag"); | ||
| 6639 | break; | ||
| 6640 | default: | ||
| 6641 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); | ||
| 6642 | break; | ||
| 6643 | } | ||
| 6644 | |||
| 6645 | this.AddChildToParent("Component", xIniFile, row, 7); | ||
| 6646 | } | ||
| 6647 | } | ||
| 6648 | |||
| 6649 | /// <summary> | ||
| 6650 | /// Decompile the RemoveRegistry table. | ||
| 6651 | /// </summary> | ||
| 6652 | /// <param name="table">The table to decompile.</param> | ||
| 6653 | private void DecompileRemoveRegistryTable(Table table) | ||
| 6654 | { | ||
| 6655 | foreach (var row in table.Rows) | ||
| 6656 | { | ||
| 6657 | if ("-" == row.FieldAsString(3)) | ||
| 6658 | { | ||
| 6659 | var xRemoveRegistryKey = new XElement(Names.RemoveRegistryKeyElement, | ||
| 6660 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6661 | new XAttribute("Key", row.FieldAsString(2)), | ||
| 6662 | new XAttribute("Action", "removeOnInstall")); | ||
| 6663 | |||
| 6664 | if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) | ||
| 6665 | { | ||
| 6666 | xRemoveRegistryKey.SetAttributeValue("Root", registryRootType); | ||
| 6667 | } | ||
| 6668 | |||
| 6669 | this.AddChildToParent("Component", xRemoveRegistryKey, row, 4); | ||
| 6670 | } | ||
| 6671 | else | ||
| 6672 | { | ||
| 6673 | var xRemoveRegistryValue = new XElement(Names.RemoveRegistryValueElement, | ||
| 6674 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6675 | new XAttribute("Key", row.FieldAsString(2)), | ||
| 6676 | XAttributeIfNotNull("Name", row, 3)); | ||
| 6677 | |||
| 6678 | if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) | ||
| 6679 | { | ||
| 6680 | xRemoveRegistryValue.SetAttributeValue("Root", registryRootType); | ||
| 6681 | } | ||
| 6682 | |||
| 6683 | this.AddChildToParent("Component", xRemoveRegistryValue, row, 4); | ||
| 6684 | } | ||
| 6685 | } | ||
| 6686 | } | ||
| 6687 | |||
| 6688 | /// <summary> | ||
| 6689 | /// Decompile the ReserveCost table. | ||
| 6690 | /// </summary> | ||
| 6691 | /// <param name="table">The table to decompile.</param> | ||
| 6692 | private void DecompileReserveCostTable(Table table) | ||
| 6693 | { | ||
| 6694 | foreach (var row in table.Rows) | ||
| 6695 | { | ||
| 6696 | var xReserveCost = new XElement(Names.ReserveCostElement, | ||
| 6697 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6698 | XAttributeIfNotNull("Directory", row, 2), | ||
| 6699 | new XAttribute("RunLocal", row.FieldAsString(3)), | ||
| 6700 | new XAttribute("RunFromSource", row.FieldAsString(4))); | ||
| 6701 | |||
| 6702 | this.AddChildToParent("Component", xReserveCost, row, 4); | ||
| 6703 | } | ||
| 6704 | } | ||
| 6705 | |||
| 6706 | /// <summary> | ||
| 6707 | /// Decompile the SelfReg table. | ||
| 6708 | /// </summary> | ||
| 6709 | /// <param name="table">The table to decompile.</param> | ||
| 6710 | private void DecompileSelfRegTable(Table table) | ||
| 6711 | { | ||
| 6712 | foreach (var row in table.Rows) | ||
| 6713 | { | ||
| 6714 | if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) | ||
| 6715 | { | ||
| 6716 | xFile.SetAttributeValue("SelfRegCost", row.IsColumnNull(1) ? 0 : row.FieldAsInteger(1)); | ||
| 6717 | } | ||
| 6718 | else | ||
| 6719 | { | ||
| 6720 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); | ||
| 6721 | } | ||
| 6722 | } | ||
| 6723 | } | ||
| 6724 | |||
| 6725 | /// <summary> | ||
| 6726 | /// Decompile the ServiceControl table. | ||
| 6727 | /// </summary> | ||
| 6728 | /// <param name="table">The table to decompile.</param> | ||
| 6729 | private void DecompileServiceControlTable(Table table) | ||
| 6730 | { | ||
| 6731 | foreach (var row in table.Rows) | ||
| 6732 | { | ||
| 6733 | var xServiceControl = new XElement(Names.ServiceControlElement, | ||
| 6734 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6735 | new XAttribute("Name", row.FieldAsString(1))); | ||
| 6736 | |||
| 6737 | var eventValue = row.FieldAsInteger(2); | ||
| 6738 | if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart) && | ||
| 6739 | WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) | ||
| 6740 | { | ||
| 6741 | xServiceControl.SetAttributeValue("Start", "both"); | ||
| 6742 | } | ||
| 6743 | else if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart)) | ||
| 6744 | { | ||
| 6745 | xServiceControl.SetAttributeValue("Start", "install"); | ||
| 6746 | } | ||
| 6747 | else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) | ||
| 6748 | { | ||
| 6749 | xServiceControl.SetAttributeValue("Start", "uninstall"); | ||
| 6750 | } | ||
| 6751 | |||
| 6752 | if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop) && | ||
| 6753 | WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) | ||
| 6754 | { | ||
| 6755 | xServiceControl.SetAttributeValue("Stop", "both"); | ||
| 6756 | } | ||
| 6757 | else if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop)) | ||
| 6758 | { | ||
| 6759 | xServiceControl.SetAttributeValue("Stop", "install"); | ||
| 6760 | } | ||
| 6761 | else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) | ||
| 6762 | { | ||
| 6763 | xServiceControl.SetAttributeValue("Stop", "uninstall"); | ||
| 6764 | } | ||
| 6765 | |||
| 6766 | if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete) && | ||
| 6767 | WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) | ||
| 6768 | { | ||
| 6769 | xServiceControl.SetAttributeValue("Remove", "both"); | ||
| 6770 | } | ||
| 6771 | else if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete)) | ||
| 6772 | { | ||
| 6773 | xServiceControl.SetAttributeValue("Remove", "install"); | ||
| 6774 | } | ||
| 6775 | else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) | ||
| 6776 | { | ||
| 6777 | xServiceControl.SetAttributeValue("Remove", "uninstall"); | ||
| 6778 | } | ||
| 6779 | |||
| 6780 | if (!row.IsColumnNull(3)) | ||
| 6781 | { | ||
| 6782 | var arguments = NullSplitter.Split(row.FieldAsString(3)); | ||
| 6783 | |||
| 6784 | foreach (var argument in arguments) | ||
| 6785 | { | ||
| 6786 | var xServiceArgument = new XElement(Names.ServiceArgumentElement, | ||
| 6787 | new XAttribute("Value", argument)); | ||
| 6788 | |||
| 6789 | xServiceControl.Add(xServiceArgument); | ||
| 6790 | } | ||
| 6791 | } | ||
| 6792 | |||
| 6793 | if (!row.IsColumnNull(4)) | ||
| 6794 | { | ||
| 6795 | xServiceControl.SetAttributeValue("Wait", row.FieldAsInteger(4) == 0 ? "no" : "yes"); | ||
| 6796 | } | ||
| 6797 | |||
| 6798 | this.AddChildToParent("Component", xServiceControl, row, 5); | ||
| 6799 | } | ||
| 6800 | } | ||
| 6801 | |||
| 6802 | /// <summary> | ||
| 6803 | /// Decompile the ServiceInstall table. | ||
| 6804 | /// </summary> | ||
| 6805 | /// <param name="table">The table to decompile.</param> | ||
| 6806 | private void DecompileServiceInstallTable(Table table) | ||
| 6807 | { | ||
| 6808 | foreach (var row in table.Rows) | ||
| 6809 | { | ||
| 6810 | var xServiceInstall = new XElement(Names.ServiceInstallElement, | ||
| 6811 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6812 | new XAttribute("Name", row.FieldAsString(1)), | ||
| 6813 | XAttributeIfNotNull("DisplayName", row, 2), | ||
| 6814 | XAttributeIfNotNull("LoadOrderGroup", row, 6), | ||
| 6815 | XAttributeIfNotNull("Account", row, 8), | ||
| 6816 | XAttributeIfNotNull("Password", row, 9), | ||
| 6817 | XAttributeIfNotNull("Arguments", row, 10), | ||
| 6818 | XAttributeIfNotNull("Description", row, 12)); | ||
| 6819 | |||
| 6820 | var serviceType = row.FieldAsInteger(3); | ||
| 6821 | if (WindowsInstallerConstants.MsidbServiceInstallInteractive == (serviceType & WindowsInstallerConstants.MsidbServiceInstallInteractive)) | ||
| 6822 | { | ||
| 6823 | xServiceInstall.SetAttributeValue("Interactive", "yes"); | ||
| 6824 | } | ||
| 6825 | |||
| 6826 | if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess) && | ||
| 6827 | WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) | ||
| 6828 | { | ||
| 6829 | // TODO: warn | ||
| 6830 | } | ||
| 6831 | else if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess)) | ||
| 6832 | { | ||
| 6833 | xServiceInstall.SetAttributeValue("Type", "ownProcess"); | ||
| 6834 | } | ||
| 6835 | else if (WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) | ||
| 6836 | { | ||
| 6837 | xServiceInstall.SetAttributeValue("Type", "shareProcess"); | ||
| 6838 | } | ||
| 6839 | |||
| 6840 | var startType = row.FieldAsInteger(4); | ||
| 6841 | if (WindowsInstallerConstants.MsidbServiceInstallDisabled == startType) | ||
| 6842 | { | ||
| 6843 | xServiceInstall.SetAttributeValue("Start", "disabled"); | ||
| 6844 | } | ||
| 6845 | else if (WindowsInstallerConstants.MsidbServiceInstallDemandStart == startType) | ||
| 6846 | { | ||
| 6847 | xServiceInstall.SetAttributeValue("Start", "demand"); | ||
| 6848 | } | ||
| 6849 | else if (WindowsInstallerConstants.MsidbServiceInstallAutoStart == startType) | ||
| 6850 | { | ||
| 6851 | xServiceInstall.SetAttributeValue("Start", "auto"); | ||
| 6852 | } | ||
| 6853 | else | ||
| 6854 | { | ||
| 6855 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); | ||
| 6856 | } | ||
| 6857 | |||
| 6858 | var errorControl = row.FieldAsInteger(5); | ||
| 6859 | if (WindowsInstallerConstants.MsidbServiceInstallErrorCritical == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorCritical)) | ||
| 6860 | { | ||
| 6861 | xServiceInstall.SetAttributeValue("ErrorControl", "critical"); | ||
| 6862 | } | ||
| 6863 | else if (WindowsInstallerConstants.MsidbServiceInstallErrorNormal == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorNormal)) | ||
| 6864 | { | ||
| 6865 | xServiceInstall.SetAttributeValue("ErrorControl", "normal"); | ||
| 6866 | } | ||
| 6867 | else | ||
| 6868 | { | ||
| 6869 | xServiceInstall.SetAttributeValue("ErrorControl", "ignore"); | ||
| 6870 | } | ||
| 6871 | |||
| 6872 | if (WindowsInstallerConstants.MsidbServiceInstallErrorControlVital == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorControlVital)) | ||
| 6873 | { | ||
| 6874 | xServiceInstall.SetAttributeValue("Vital", "yes"); | ||
| 6875 | } | ||
| 6876 | |||
| 6877 | if (!row.IsColumnNull(7)) | ||
| 6878 | { | ||
| 6879 | var dependencies = NullSplitter.Split(row.FieldAsString(7)); | ||
| 6880 | |||
| 6881 | foreach (var dependency in dependencies) | ||
| 6882 | { | ||
| 6883 | if (0 < dependency.Length) | ||
| 6884 | { | ||
| 6885 | var xServiceDependency = new XElement(Names.ServiceDependencyElement); | ||
| 6886 | |||
| 6887 | if (dependency.StartsWith("+", StringComparison.Ordinal)) | ||
| 6888 | { | ||
| 6889 | xServiceDependency.SetAttributeValue("Group", "yes"); | ||
| 6890 | xServiceDependency.SetAttributeValue("Id", dependency.Substring(1)); | ||
| 6891 | } | ||
| 6892 | else | ||
| 6893 | { | ||
| 6894 | xServiceDependency.SetAttributeValue("Id", dependency); | ||
| 6895 | } | ||
| 6896 | |||
| 6897 | xServiceInstall.Add(xServiceDependency); | ||
| 6898 | } | ||
| 6899 | } | ||
| 6900 | } | ||
| 6901 | |||
| 6902 | this.AddChildToParent("Component", xServiceInstall, row, 11); | ||
| 6903 | this.IndexElement(row, xServiceInstall); | ||
| 6904 | } | ||
| 6905 | } | ||
| 6906 | |||
| 6907 | /// <summary> | ||
| 6908 | /// Decompile the SFPCatalog table. | ||
| 6909 | /// </summary> | ||
| 6910 | /// <param name="table">The table to decompile.</param> | ||
| 6911 | private void DecompileSFPCatalogTable(Table table) | ||
| 6912 | { | ||
| 6913 | foreach (var row in table.Rows) | ||
| 6914 | { | ||
| 6915 | var xSfpCatalog = new XElement(Names.SFPCatalogElement, | ||
| 6916 | new XAttribute("Name", row.FieldAsString(0)), | ||
| 6917 | new XAttribute("SourceFile", row.FieldAsString(1))); | ||
| 6918 | |||
| 6919 | this.IndexElement(row, xSfpCatalog); | ||
| 6920 | } | ||
| 6921 | |||
| 6922 | // nest the SFPCatalog elements | ||
| 6923 | foreach (var row in table.Rows) | ||
| 6924 | { | ||
| 6925 | var xSfpCatalog = this.GetIndexedElement(row); | ||
| 6926 | |||
| 6927 | if (!row.IsColumnNull(2)) | ||
| 6928 | { | ||
| 6929 | if (this.TryGetIndexedElement("SFPCatalog", out var xParentSFPCatalog, row.FieldAsString(2))) | ||
| 6930 | { | ||
| 6931 | xParentSFPCatalog.Add(xSfpCatalog); | ||
| 6932 | } | ||
| 6933 | else | ||
| 6934 | { | ||
| 6935 | xSfpCatalog.SetAttributeValue("Dependency", row.FieldAsString(2)); | ||
| 6936 | |||
| 6937 | this.RootElement.Add(xSfpCatalog); | ||
| 6938 | } | ||
| 6939 | } | ||
| 6940 | else | ||
| 6941 | { | ||
| 6942 | this.RootElement.Add(xSfpCatalog); | ||
| 6943 | } | ||
| 6944 | } | ||
| 6945 | } | ||
| 6946 | |||
| 6947 | /// <summary> | ||
| 6948 | /// Decompile the Shortcut table. | ||
| 6949 | /// </summary> | ||
| 6950 | /// <param name="table">The table to decompile.</param> | ||
| 6951 | private void DecompileShortcutTable(Table table) | ||
| 6952 | { | ||
| 6953 | foreach (var row in table.Rows) | ||
| 6954 | { | ||
| 6955 | var xShortcut = new XElement(Names.ShortcutElement, | ||
| 6956 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 6957 | new XAttribute("Directory", row.FieldAsString(1)), | ||
| 6958 | XAttributeIfNotNull("Arguments", row, 5), | ||
| 6959 | XAttributeIfNotNull("Description", row, 6), | ||
| 6960 | XAttributeIfNotNull("Hotkey", row, 7), | ||
| 6961 | XAttributeIfNotNull("Icon", row, 8), | ||
| 6962 | XAttributeIfNotNull("IconIndex", row, 9), | ||
| 6963 | XAttributeIfNotNull("WorkingDirectory", row, 11)); | ||
| 6964 | |||
| 6965 | var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); | ||
| 6966 | if (null != names[0] && null != names[1]) | ||
| 6967 | { | ||
| 6968 | xShortcut.SetAttributeValue("ShortName", names[0]); | ||
| 6969 | xShortcut.SetAttributeValue("Name", names[1]); | ||
| 6970 | } | ||
| 6971 | else if (null != names[0]) | ||
| 6972 | { | ||
| 6973 | xShortcut.SetAttributeValue("Name", names[0]); | ||
| 6974 | } | ||
| 6975 | |||
| 6976 | if (!row.IsColumnNull(10)) | ||
| 6977 | { | ||
| 6978 | switch (row.FieldAsInteger(10)) | ||
| 6979 | { | ||
| 6980 | case 1: | ||
| 6981 | xShortcut.SetAttributeValue("Show", "normal"); | ||
| 6982 | break; | ||
| 6983 | case 3: | ||
| 6984 | xShortcut.SetAttributeValue("Show", "maximized"); | ||
| 6985 | break; | ||
| 6986 | case 7: | ||
| 6987 | xShortcut.SetAttributeValue("Show", "minimized"); | ||
| 6988 | break; | ||
| 6989 | default: | ||
| 6990 | this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); | ||
| 6991 | break; | ||
| 6992 | } | ||
| 6993 | } | ||
| 6994 | |||
| 6995 | // Only try to read the MSI 4.0-specific columns if they actually exist | ||
| 6996 | if (15 < row.Fields.Length) | ||
| 6997 | { | ||
| 6998 | if (!row.IsColumnNull(12)) | ||
| 6999 | { | ||
| 7000 | xShortcut.SetAttributeValue("DisplayResourceDll", row.FieldAsString(12)); | ||
| 7001 | } | ||
| 7002 | |||
| 7003 | if (null != row[13]) | ||
| 7004 | { | ||
| 7005 | xShortcut.SetAttributeValue("DisplayResourceId", row.FieldAsInteger(13)); | ||
| 7006 | } | ||
| 7007 | |||
| 7008 | if (null != row[14]) | ||
| 7009 | { | ||
| 7010 | xShortcut.SetAttributeValue("DescriptionResourceDll", row.FieldAsString(14)); | ||
| 7011 | } | ||
| 7012 | |||
| 7013 | if (null != row[15]) | ||
| 7014 | { | ||
| 7015 | xShortcut.SetAttributeValue("DescriptionResourceId", row.FieldAsInteger(15)); | ||
| 7016 | } | ||
| 7017 | } | ||
| 7018 | |||
| 7019 | this.AddChildToParent("Component", xShortcut, row, 3); | ||
| 7020 | this.IndexElement(row, xShortcut); | ||
| 7021 | } | ||
| 7022 | } | ||
| 7023 | |||
| 7024 | /// <summary> | ||
| 7025 | /// Decompile the Signature table. | ||
| 7026 | /// </summary> | ||
| 7027 | /// <param name="table">The table to decompile.</param> | ||
| 7028 | private void DecompileSignatureTable(Table table) | ||
| 7029 | { | ||
| 7030 | foreach (var row in table.Rows) | ||
| 7031 | { | ||
| 7032 | var fileSearch = new XElement(Names.FileSearchElement, | ||
| 7033 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 7034 | XAttributeIfNotNull("MinVersion", row, 2), | ||
| 7035 | XAttributeIfNotNull("MaxVersion", row, 3), | ||
| 7036 | XAttributeIfNotNull("MinSize", row, 4), | ||
| 7037 | XAttributeIfNotNull("MaxSize", row, 5), | ||
| 7038 | XAttributeIfNotNull("Languages", row, 8)); | ||
| 7039 | |||
| 7040 | var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); | ||
| 7041 | if (null != names[0]) | ||
| 7042 | { | ||
| 7043 | // it is permissable to just have a long name | ||
| 7044 | if (!this.BackendHelper.IsValidShortFilename(names[0], false) && null == names[1]) | ||
| 7045 | { | ||
| 7046 | fileSearch.SetAttributeValue("Name", names[0]); | ||
| 7047 | } | ||
| 7048 | else | ||
| 7049 | { | ||
| 7050 | fileSearch.SetAttributeValue("ShortName", names[0]); | ||
| 7051 | } | ||
| 7052 | } | ||
| 7053 | |||
| 7054 | if (null != names[1]) | ||
| 7055 | { | ||
| 7056 | fileSearch.SetAttributeValue("Name", names[1]); | ||
| 7057 | } | ||
| 7058 | |||
| 7059 | if (!row.IsColumnNull(6)) | ||
| 7060 | { | ||
| 7061 | fileSearch.SetAttributeValue("MinDate", ConvertIntegerToDateTime(row.FieldAsInteger(6))); | ||
| 7062 | } | ||
| 7063 | |||
| 7064 | if (!row.IsColumnNull(7)) | ||
| 7065 | { | ||
| 7066 | fileSearch.SetAttributeValue("MaxDate", ConvertIntegerToDateTime(row.FieldAsInteger(7))); | ||
| 7067 | } | ||
| 7068 | |||
| 7069 | this.IndexElement(row, fileSearch); | ||
| 7070 | } | ||
| 7071 | } | ||
| 7072 | |||
| 7073 | /// <summary> | ||
| 7074 | /// Decompile the TargetFiles_OptionalData table. | ||
| 7075 | /// </summary> | ||
| 7076 | /// <param name="table">The table to decompile.</param> | ||
| 7077 | private void DecompileTargetFiles_OptionalDataTable(Table table) | ||
| 7078 | { | ||
| 7079 | foreach (var row in table.Rows) | ||
| 7080 | { | ||
| 7081 | if (!this.PatchTargetFiles.TryGetValue(row.FieldAsString(0), out var xPatchTargetFile)) | ||
| 7082 | { | ||
| 7083 | xPatchTargetFile = new XElement(Names.TargetFileElement, | ||
| 7084 | new XAttribute("Id", row.FieldAsString(1))); | ||
| 7085 | |||
| 7086 | if (this.TryGetIndexedElement("TargetImages", out var xTargetImage, row.FieldAsString(0))) | ||
| 7087 | { | ||
| 7088 | xTargetImage.Add(xPatchTargetFile); | ||
| 7089 | } | ||
| 7090 | else | ||
| 7091 | { | ||
| 7092 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); | ||
| 7093 | } | ||
| 7094 | |||
| 7095 | this.PatchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), xPatchTargetFile); | ||
| 7096 | } | ||
| 7097 | |||
| 7098 | AddSymbolPaths(row, 2, xPatchTargetFile); | ||
| 7099 | |||
| 7100 | if (!row.IsColumnNull(3) && !row.IsColumnNull(4)) | ||
| 7101 | { | ||
| 7102 | var ignoreOffsets = row.FieldAsString(3).Split(','); | ||
| 7103 | var ignoreLengths = row.FieldAsString(4).Split(','); | ||
| 7104 | |||
| 7105 | if (ignoreOffsets.Length == ignoreLengths.Length) | ||
| 7106 | { | ||
| 7107 | for (var i = 0; i < ignoreOffsets.Length; i++) | ||
| 7108 | { | ||
| 7109 | var xIgnoreRange = new XElement(Names.IgnoreRangeElement); | ||
| 7110 | |||
| 7111 | if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) | ||
| 7112 | { | ||
| 7113 | xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); | ||
| 7114 | } | ||
| 7115 | else | ||
| 7116 | { | ||
| 7117 | xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); | ||
| 7118 | } | ||
| 7119 | |||
| 7120 | if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) | ||
| 7121 | { | ||
| 7122 | xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); | ||
| 7123 | } | ||
| 7124 | else | ||
| 7125 | { | ||
| 7126 | xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); | ||
| 7127 | } | ||
| 7128 | |||
| 7129 | xPatchTargetFile.Add(xIgnoreRange); | ||
| 7130 | } | ||
| 7131 | } | ||
| 7132 | else | ||
| 7133 | { | ||
| 7134 | // TODO: warn | ||
| 7135 | } | ||
| 7136 | } | ||
| 7137 | else if (!row.IsColumnNull(3) || !row.IsColumnNull(4)) | ||
| 7138 | { | ||
| 7139 | // TODO: warn about mismatch between columns | ||
| 7140 | } | ||
| 7141 | |||
| 7142 | // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable | ||
| 7143 | } | ||
| 7144 | } | ||
| 7145 | |||
| 7146 | /// <summary> | ||
| 7147 | /// Decompile the TargetImages table. | ||
| 7148 | /// </summary> | ||
| 7149 | /// <param name="table">The table to decompile.</param> | ||
| 7150 | private void DecompileTargetImagesTable(Table table) | ||
| 7151 | { | ||
| 7152 | foreach (var row in table.Rows) | ||
| 7153 | { | ||
| 7154 | var xTargetImage = new XElement(Names.TargetImageElement, | ||
| 7155 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 7156 | new XAttribute("SourceFile", row.FieldAsString(1)), | ||
| 7157 | new XAttribute("Order", row.FieldAsInteger(4)), | ||
| 7158 | XAttributeIfNotNull("Validation", row, 5)); | ||
| 7159 | |||
| 7160 | AddSymbolPaths(row, 2, xTargetImage); | ||
| 7161 | |||
| 7162 | if (0 != row.FieldAsInteger(6)) | ||
| 7163 | { | ||
| 7164 | xTargetImage.SetAttributeValue("IgnoreMissingFiles", "yes"); | ||
| 7165 | } | ||
| 7166 | |||
| 7167 | this.AddChildToParent("UpgradedImages", xTargetImage, row, 3); | ||
| 7168 | this.IndexElement(row, xTargetImage); | ||
| 7169 | } | ||
| 7170 | } | ||
| 7171 | |||
| 7172 | /// <summary> | ||
| 7173 | /// Decompile the TextStyle table. | ||
| 7174 | /// </summary> | ||
| 7175 | /// <param name="table">The table to decompile.</param> | ||
| 7176 | private void DecompileTextStyleTable(Table table) | ||
| 7177 | { | ||
| 7178 | foreach (var row in table.Rows) | ||
| 7179 | { | ||
| 7180 | var xTextStyle = new XElement(Names.TextStyleElement, | ||
| 7181 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 7182 | new XAttribute("FaceName", row.FieldAsString(1)), | ||
| 7183 | new XAttribute("Size", row.FieldAsString(2))); | ||
| 7184 | |||
| 7185 | if (!row.IsColumnNull(3)) | ||
| 7186 | { | ||
| 7187 | var color = row.FieldAsInteger(3); | ||
| 7188 | |||
| 7189 | xTextStyle.SetAttributeValue("Red", color & 0xFF); | ||
| 7190 | xTextStyle.SetAttributeValue("Green", (color & 0xFF00) >> 8); | ||
| 7191 | xTextStyle.SetAttributeValue("Blue", (color & 0xFF0000) >> 16); | ||
| 7192 | } | ||
| 7193 | |||
| 7194 | if (!row.IsColumnNull(4)) | ||
| 7195 | { | ||
| 7196 | var styleBits = row.FieldAsInteger(4); | ||
| 7197 | |||
| 7198 | if (WindowsInstallerConstants.MsidbTextStyleStyleBitsBold == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsBold)) | ||
| 7199 | { | ||
| 7200 | xTextStyle.SetAttributeValue("Bold", "yes"); | ||
| 7201 | } | ||
| 7202 | |||
| 7203 | if (WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic)) | ||
| 7204 | { | ||
| 7205 | xTextStyle.SetAttributeValue("Italic", "yes"); | ||
| 7206 | } | ||
| 7207 | |||
| 7208 | if (WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline)) | ||
| 7209 | { | ||
| 7210 | xTextStyle.SetAttributeValue("Underline", "yes"); | ||
| 7211 | } | ||
| 7212 | |||
| 7213 | if (WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike)) | ||
| 7214 | { | ||
| 7215 | xTextStyle.SetAttributeValue("Strike", "yes"); | ||
| 7216 | } | ||
| 7217 | } | ||
| 7218 | |||
| 7219 | this.UIElement.Add(xTextStyle); | ||
| 7220 | } | ||
| 7221 | } | ||
| 7222 | |||
| 7223 | /// <summary> | ||
| 7224 | /// Decompile the TypeLib table. | ||
| 7225 | /// </summary> | ||
| 7226 | /// <param name="table">The table to decompile.</param> | ||
| 7227 | private void DecompileTypeLibTable(Table table) | ||
| 7228 | { | ||
| 7229 | foreach (var row in table.Rows) | ||
| 7230 | { | ||
| 7231 | var id = row.FieldAsString(0); | ||
| 7232 | var xTypeLib = new XElement(Names.TypeLibElement, | ||
| 7233 | new XAttribute("Advertise", "yes"), | ||
| 7234 | new XAttribute("Id", id), | ||
| 7235 | new XAttribute("Language", row.FieldAsInteger(1)), | ||
| 7236 | XAttributeIfNotNull("Description", row, 4), | ||
| 7237 | XAttributeIfNotNull("HelpDirectory", row, 5)); | ||
| 7238 | |||
| 7239 | if (!row.IsColumnNull(3)) | ||
| 7240 | { | ||
| 7241 | var version = row.FieldAsInteger(3); | ||
| 7242 | |||
| 7243 | if (65536 == version) | ||
| 7244 | { | ||
| 7245 | this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, id)); | ||
| 7246 | } | ||
| 7247 | |||
| 7248 | xTypeLib.SetAttributeValue("MajorVersion", (version & 0xFFFF00) >> 8); | ||
| 7249 | xTypeLib.SetAttributeValue("MinorVersion", version & 0xFF); | ||
| 7250 | } | ||
| 7251 | |||
| 7252 | if (!row.IsColumnNull(7)) | ||
| 7253 | { | ||
| 7254 | xTypeLib.SetAttributeValue("Cost", row.FieldAsInteger(7)); | ||
| 7255 | } | ||
| 7256 | |||
| 7257 | // nested under the appropriate File element in FinalizeFileTable | ||
| 7258 | this.IndexElement(row, xTypeLib); | ||
| 7259 | } | ||
| 7260 | } | ||
| 7261 | |||
| 7262 | /// <summary> | ||
| 7263 | /// Decompile the Upgrade table. | ||
| 7264 | /// </summary> | ||
| 7265 | /// <param name="table">The table to decompile.</param> | ||
| 7266 | private void DecompileUpgradeTable(Table table) | ||
| 7267 | { | ||
| 7268 | var xUpgrades = new Dictionary<string, XElement>(); | ||
| 7269 | |||
| 7270 | foreach (UpgradeRow upgradeRow in table.Rows) | ||
| 7271 | { | ||
| 7272 | if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty || WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) | ||
| 7273 | { | ||
| 7274 | continue; // MajorUpgrade rows processed in FinalizeUpgradeTable | ||
| 7275 | } | ||
| 7276 | |||
| 7277 | if (!xUpgrades.TryGetValue(upgradeRow.UpgradeCode, out var xUpgrade)) | ||
| 7278 | { | ||
| 7279 | xUpgrade = new XElement(Names.UpgradeElement, | ||
| 7280 | new XAttribute("Id", upgradeRow.UpgradeCode)); | ||
| 7281 | |||
| 7282 | this.RootElement.Add(xUpgrade); | ||
| 7283 | xUpgrades.Add(upgradeRow.UpgradeCode, xUpgrade); | ||
| 7284 | } | ||
| 7285 | |||
| 7286 | var xUpgradeVersion = new XElement(Names.UpgradeVersionElement, | ||
| 7287 | new XAttribute("Id", upgradeRow.UpgradeCode), | ||
| 7288 | new XAttribute("Property", upgradeRow.ActionProperty)); | ||
| 7289 | |||
| 7290 | if (null != upgradeRow.VersionMin) | ||
| 7291 | { | ||
| 7292 | xUpgradeVersion.SetAttributeValue("Minimum", upgradeRow.VersionMin); | ||
| 7293 | } | ||
| 7294 | |||
| 7295 | if (null != upgradeRow.VersionMax) | ||
| 7296 | { | ||
| 7297 | xUpgradeVersion.SetAttributeValue("Maximum", upgradeRow.VersionMax); | ||
| 7298 | } | ||
| 7299 | |||
| 7300 | if (null != upgradeRow.Language) | ||
| 7301 | { | ||
| 7302 | xUpgradeVersion.SetAttributeValue("Language", upgradeRow.Language); | ||
| 7303 | } | ||
| 7304 | |||
| 7305 | if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) | ||
| 7306 | { | ||
| 7307 | xUpgradeVersion.SetAttributeValue("MigrateFeatures", "yes"); | ||
| 7308 | } | ||
| 7309 | |||
| 7310 | if (WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect)) | ||
| 7311 | { | ||
| 7312 | xUpgradeVersion.SetAttributeValue("OnlyDetect", "yes"); | ||
| 7313 | } | ||
| 7314 | |||
| 7315 | if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) | ||
| 7316 | { | ||
| 7317 | xUpgradeVersion.SetAttributeValue("IgnoreRemoveFailure", "yes"); | ||
| 7318 | } | ||
| 7319 | |||
| 7320 | if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive)) | ||
| 7321 | { | ||
| 7322 | xUpgradeVersion.SetAttributeValue("IncludeMinimum", "yes"); | ||
| 7323 | } | ||
| 7324 | |||
| 7325 | if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) | ||
| 7326 | { | ||
| 7327 | xUpgradeVersion.SetAttributeValue("IncludeMaximum", "yes"); | ||
| 7328 | } | ||
| 7329 | |||
| 7330 | if (WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive)) | ||
| 7331 | { | ||
| 7332 | xUpgradeVersion.SetAttributeValue("ExcludeLanguages", "yes"); | ||
| 7333 | } | ||
| 7334 | |||
| 7335 | if (null != upgradeRow.Remove) | ||
| 7336 | { | ||
| 7337 | xUpgradeVersion.SetAttributeValue("RemoveFeatures", upgradeRow.Remove); | ||
| 7338 | } | ||
| 7339 | |||
| 7340 | xUpgrade.Add(xUpgradeVersion); | ||
| 7341 | } | ||
| 7342 | } | ||
| 7343 | |||
| 7344 | /// <summary> | ||
| 7345 | /// Decompile the UpgradedFiles_OptionalData table. | ||
| 7346 | /// </summary> | ||
| 7347 | /// <param name="table">The table to decompile.</param> | ||
| 7348 | private void DecompileUpgradedFiles_OptionalDataTable(Table table) | ||
| 7349 | { | ||
| 7350 | foreach (var row in table.Rows) | ||
| 7351 | { | ||
| 7352 | var xUpgradeFile = new XElement(Names.UpgradeFileElement, | ||
| 7353 | new XAttribute("File", row.FieldAsString(1)), | ||
| 7354 | new XAttribute("Ignore", "no")); | ||
| 7355 | |||
| 7356 | AddSymbolPaths(row, 2, xUpgradeFile); | ||
| 7357 | |||
| 7358 | if (!row.IsColumnNull(3) && 1 == row.FieldAsInteger(3)) | ||
| 7359 | { | ||
| 7360 | xUpgradeFile.SetAttributeValue("AllowIgnoreOnError", "yes"); | ||
| 7361 | } | ||
| 7362 | |||
| 7363 | if (!row.IsColumnNull(4) && 0 != row.FieldAsInteger(4)) | ||
| 7364 | { | ||
| 7365 | xUpgradeFile.SetAttributeValue("WholeFile", "yes"); | ||
| 7366 | } | ||
| 7367 | |||
| 7368 | this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); | ||
| 7369 | } | ||
| 7370 | } | ||
| 7371 | |||
| 7372 | /// <summary> | ||
| 7373 | /// Decompile the UpgradedFilesToIgnore table. | ||
| 7374 | /// </summary> | ||
| 7375 | /// <param name="table">The table to decompile.</param> | ||
| 7376 | private void DecompileUpgradedFilesToIgnoreTable(Table table) | ||
| 7377 | { | ||
| 7378 | foreach (var row in table.Rows) | ||
| 7379 | { | ||
| 7380 | if ("*" != row.FieldAsString(0)) | ||
| 7381 | { | ||
| 7382 | var xUpgradeFile = new XElement(Names.UpgradeFileElement, | ||
| 7383 | new XAttribute("File", row.FieldAsString(1)), | ||
| 7384 | new XAttribute("Ignore", "yes")); | ||
| 7385 | |||
| 7386 | this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); | ||
| 7387 | } | ||
| 7388 | else | ||
| 7389 | { | ||
| 7390 | this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0])); | ||
| 7391 | } | ||
| 7392 | } | ||
| 7393 | } | ||
| 7394 | |||
| 7395 | /// <summary> | ||
| 7396 | /// Decompile the UpgradedImages table. | ||
| 7397 | /// </summary> | ||
| 7398 | /// <param name="table">The table to decompile.</param> | ||
| 7399 | private void DecompileUpgradedImagesTable(Table table) | ||
| 7400 | { | ||
| 7401 | foreach (var row in table.Rows) | ||
| 7402 | { | ||
| 7403 | var xUpgradeImage = new XElement(Names.UpgradeImageElement, | ||
| 7404 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 7405 | new XAttribute("SourceFile", row.FieldAsString(1)), | ||
| 7406 | XAttributeIfNotNull("SourcePatch", row, 2)); | ||
| 7407 | |||
| 7408 | AddSymbolPaths(row, 3, xUpgradeImage); | ||
| 7409 | |||
| 7410 | this.AddChildToParent("ImageFamilies", xUpgradeImage, row, 4); | ||
| 7411 | this.IndexElement(row, xUpgradeImage); | ||
| 7412 | } | ||
| 7413 | } | ||
| 7414 | |||
| 7415 | private static void AddSymbolPaths(Row row, int column, XElement xParent) | ||
| 7416 | { | ||
| 7417 | if (!row.IsColumnNull(column)) | ||
| 7418 | { | ||
| 7419 | var symbolPaths = row.FieldAsString(column).Split(';'); | ||
| 7420 | |||
| 7421 | foreach (var symbolPath in symbolPaths) | ||
| 7422 | { | ||
| 7423 | var xSymbolPath = new XElement(Names.SymbolPathElement, | ||
| 7424 | new XAttribute("Path", symbolPath)); | ||
| 7425 | |||
| 7426 | xParent.Add(xSymbolPath); | ||
| 7427 | } | ||
| 7428 | } | ||
| 7429 | } | ||
| 7430 | |||
| 7431 | /// <summary> | ||
| 7432 | /// Decompile the UIText table. | ||
| 7433 | /// </summary> | ||
| 7434 | /// <param name="table">The table to decompile.</param> | ||
| 7435 | private void DecompileUITextTable(Table table) | ||
| 7436 | { | ||
| 7437 | foreach (var row in table.Rows) | ||
| 7438 | { | ||
| 7439 | var xUiText = new XElement(Names.UITextElement, | ||
| 7440 | new XAttribute("Id", row.FieldAsString(0)), | ||
| 7441 | new XAttribute("Value", row.FieldAsString(1))); | ||
| 7442 | |||
| 7443 | this.UIElement.Add(xUiText); | ||
| 7444 | } | ||
| 7445 | } | ||
| 7446 | |||
| 7447 | /// <summary> | ||
| 7448 | /// Decompile the Verb table. | ||
| 7449 | /// </summary> | ||
| 7450 | /// <param name="table">The table to decompile.</param> | ||
| 7451 | private void DecompileVerbTable(Table table) | ||
| 7452 | { | ||
| 7453 | foreach (var row in table.Rows) | ||
| 7454 | { | ||
| 7455 | var verb = new XElement(Names.VerbElement, | ||
| 7456 | new XAttribute("Id", row.FieldAsString(1)), | ||
| 7457 | XAttributeIfNotNull("Sequence", row, 2), | ||
| 7458 | XAttributeIfNotNull("Command", row, 3), | ||
| 7459 | XAttributeIfNotNull("Argument", row, 4)); | ||
| 7460 | |||
| 7461 | this.IndexElement(row, verb); | ||
| 7462 | } | ||
| 7463 | } | ||
| 7464 | |||
| 7465 | /// <summary> | ||
| 7466 | /// Gets the RegistryRootType from an integer representation of the root. | ||
| 7467 | /// </summary> | ||
| 7468 | /// <param name="sourceLineNumbers">The source line information for the root.</param> | ||
| 7469 | /// <param name="tableName">The name of the table containing the field.</param> | ||
| 7470 | /// <param name="field">The field containing the root value.</param> | ||
| 7471 | /// <param name="registryRootType">The strongly-typed representation of the root.</param> | ||
| 7472 | /// <returns>true if the value could be converted; false otherwise.</returns> | ||
| 7473 | private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out string registryRootType) | ||
| 7474 | { | ||
| 7475 | switch (Convert.ToInt32(field.Data)) | ||
| 7476 | { | ||
| 7477 | case (-1): | ||
| 7478 | registryRootType = "HKMU"; | ||
| 7479 | return true; | ||
| 7480 | case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: | ||
| 7481 | registryRootType = "HKCR"; | ||
| 7482 | return true; | ||
| 7483 | case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: | ||
| 7484 | registryRootType = "HKCU"; | ||
| 7485 | return true; | ||
| 7486 | case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: | ||
| 7487 | registryRootType = "HKLM"; | ||
| 7488 | return true; | ||
| 7489 | case WindowsInstallerConstants.MsidbRegistryRootUsers: | ||
| 7490 | registryRootType = "HKU"; | ||
| 7491 | return true; | ||
| 7492 | default: | ||
| 7493 | this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); | ||
| 7494 | registryRootType = null; // assign anything to satisfy the out parameter | ||
| 7495 | return false; | ||
| 7496 | } | ||
| 7497 | } | ||
| 7498 | |||
| 7499 | /// <summary> | ||
| 7500 | /// Set the primary feature for a component. | ||
| 7501 | /// </summary> | ||
| 7502 | /// <param name="row">The row which specifies a primary feature.</param> | ||
| 7503 | /// <param name="featureColumnIndex">The index of the column contaning the feature identifier.</param> | ||
| 7504 | /// <param name="componentColumnIndex">The index of the column containing the component identifier.</param> | ||
| 7505 | private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex) | ||
| 7506 | { | ||
| 7507 | // only products contain primary features | ||
| 7508 | if (OutputType.Product == this.OutputType) | ||
| 7509 | { | ||
| 7510 | var featureField = row.Fields[featureColumnIndex]; | ||
| 7511 | var componentField = row.Fields[componentColumnIndex]; | ||
| 7512 | |||
| 7513 | if (this.TryGetIndexedElement("FeatureComponents", out var xComponentRef, Convert.ToString(featureField.Data), Convert.ToString(componentField.Data))) | ||
| 7514 | { | ||
| 7515 | xComponentRef.SetAttributeValue("Primary", "yes"); | ||
| 7516 | } | ||
| 7517 | else | ||
| 7518 | { | ||
| 7519 | this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), featureField.Column.Name, Convert.ToString(featureField.Data), componentField.Column.Name, Convert.ToString(componentField.Data), "FeatureComponents")); | ||
| 7520 | } | ||
| 7521 | } | ||
| 7522 | } | ||
| 7523 | |||
| 7524 | /// <summary> | ||
| 7525 | /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it. | ||
| 7526 | /// </summary> | ||
| 7527 | /// <param name="tables">The collection of all tables.</param> | ||
| 7528 | private static string DetermineMajorUpgradeScheduling(TableIndexedCollection tables) | ||
| 7529 | { | ||
| 7530 | var sequenceRemoveExistingProducts = 0; | ||
| 7531 | var sequenceInstallValidate = 0; | ||
| 7532 | var sequenceInstallInitialize = 0; | ||
| 7533 | var sequenceInstallFinalize = 0; | ||
| 7534 | var sequenceInstallExecute = 0; | ||
| 7535 | var sequenceInstallExecuteAgain = 0; | ||
| 7536 | |||
| 7537 | var installExecuteSequenceTable = tables["InstallExecuteSequence"]; | ||
| 7538 | if (null != installExecuteSequenceTable) | ||
| 7539 | { | ||
| 7540 | var removeExistingProductsRow = -1; | ||
| 7541 | for (var i = 0; i < installExecuteSequenceTable.Rows.Count; i++) | ||
| 7542 | { | ||
| 7543 | var row = installExecuteSequenceTable.Rows[i]; | ||
| 7544 | var action = row.FieldAsString(0); | ||
| 7545 | var sequence = row.FieldAsInteger(2); | ||
| 7546 | |||
| 7547 | switch (action) | ||
| 7548 | { | ||
| 7549 | case "RemoveExistingProducts": | ||
| 7550 | sequenceRemoveExistingProducts = sequence; | ||
| 7551 | removeExistingProductsRow = i; | ||
| 7552 | break; | ||
| 7553 | case "InstallValidate": | ||
| 7554 | sequenceInstallValidate = sequence; | ||
| 7555 | break; | ||
| 7556 | case "InstallInitialize": | ||
| 7557 | sequenceInstallInitialize = sequence; | ||
| 7558 | break; | ||
| 7559 | case "InstallExecute": | ||
| 7560 | sequenceInstallExecute = sequence; | ||
| 7561 | break; | ||
| 7562 | case "InstallExecuteAgain": | ||
| 7563 | sequenceInstallExecuteAgain = sequence; | ||
| 7564 | break; | ||
| 7565 | case "InstallFinalize": | ||
| 7566 | sequenceInstallFinalize = sequence; | ||
| 7567 | break; | ||
| 7568 | } | ||
| 7569 | } | ||
| 7570 | |||
| 7571 | installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow); | ||
| 7572 | } | ||
| 7573 | |||
| 7574 | if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize) | ||
| 7575 | { | ||
| 7576 | return "afterInstallValidate"; | ||
| 7577 | } | ||
| 7578 | else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute) | ||
| 7579 | { | ||
| 7580 | return "afterInstallInitialize"; | ||
| 7581 | } | ||
| 7582 | else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain) | ||
| 7583 | { | ||
| 7584 | return "afterInstallExecute"; | ||
| 7585 | } | ||
| 7586 | else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize) | ||
| 7587 | { | ||
| 7588 | return "afterInstallExecuteAgain"; | ||
| 7589 | } | ||
| 7590 | else | ||
| 7591 | { | ||
| 7592 | return "afterInstallFinalize"; | ||
| 7593 | } | ||
| 7594 | } | ||
| 7595 | } | ||
| 7596 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs new file mode 100644 index 00000000..db65bbf7 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs | |||
| @@ -0,0 +1,160 @@ | |||
| 1 | namespace WixToolset.Core.WindowsInstaller.Decompile | ||
| 2 | { | ||
| 3 | using System.Xml.Linq; | ||
| 4 | |||
| 5 | internal static class Names | ||
| 6 | { | ||
| 7 | public static readonly XNamespace WxsNamespace = "http://wixtoolset.org/schemas/v4/wxs"; | ||
| 8 | |||
| 9 | public static readonly XName WixElement = WxsNamespace + "Wix"; | ||
| 10 | |||
| 11 | public static readonly XName PackageElement = WxsNamespace + "Package"; | ||
| 12 | public static readonly XName ModuleElement = WxsNamespace + "Module"; | ||
| 13 | public static readonly XName PatchCreationElement = WxsNamespace + "PatchCreation"; | ||
| 14 | |||
| 15 | public static readonly XName SummaryInformationElement = WxsNamespace + "SummaryInformation"; | ||
| 16 | |||
| 17 | public static readonly XName CustomElement = WxsNamespace + "Custom"; | ||
| 18 | |||
| 19 | public static readonly XName AdminExecuteSequenceElement = WxsNamespace + "AdminExecuteSequence"; | ||
| 20 | public static readonly XName AdminUISequenceElement = WxsNamespace + "AdminUISequence"; | ||
| 21 | public static readonly XName AdvertiseExecuteSequenceElement = WxsNamespace + "AdvertiseExecuteSequence"; | ||
| 22 | public static readonly XName InstallExecuteSequenceElement = WxsNamespace + "InstallExecuteSequence"; | ||
| 23 | public static readonly XName InstallUISequenceElement = WxsNamespace + "InstallUISequence"; | ||
| 24 | |||
| 25 | public static readonly XName AppSearchElement = WxsNamespace + "AppSearch"; | ||
| 26 | |||
| 27 | public static readonly XName PropertyElement = WxsNamespace + "Property"; | ||
| 28 | |||
| 29 | public static readonly XName ProtectRangeElement = WxsNamespace + "ProtectRange"; | ||
| 30 | public static readonly XName ProtectFileElement = WxsNamespace + "ProtectFile"; | ||
| 31 | |||
| 32 | public static readonly XName FileElement = WxsNamespace + "File"; | ||
| 33 | |||
| 34 | public static readonly XName EnsureTableElement = WxsNamespace + "EnsureTable"; | ||
| 35 | public static readonly XName PatchInformationElement = WxsNamespace + "PatchInformation"; | ||
| 36 | |||
| 37 | public static readonly XName ProgressTextElement = WxsNamespace + "ProgressText"; | ||
| 38 | public static readonly XName UIElement = WxsNamespace + "UI"; | ||
| 39 | |||
| 40 | public static readonly XName AppIdElement = WxsNamespace + "AppId"; | ||
| 41 | |||
| 42 | public static readonly XName ControlElement = WxsNamespace + "Control"; | ||
| 43 | |||
| 44 | public static readonly XName BillboardElement = WxsNamespace + "Billboard"; | ||
| 45 | public static readonly XName BillboardActionElement = WxsNamespace + "BillboardAction"; | ||
| 46 | |||
| 47 | public static readonly XName BinaryElement = WxsNamespace + "Binary"; | ||
| 48 | |||
| 49 | public static readonly XName ClassElement = WxsNamespace + "Class"; | ||
| 50 | |||
| 51 | public static readonly XName FileTypeMaskElement = WxsNamespace + "FileTypeMask"; | ||
| 52 | |||
| 53 | public static readonly XName ComboBoxElement = WxsNamespace + "ComboBox"; | ||
| 54 | |||
| 55 | public static readonly XName ListItemElement = WxsNamespace + "ListItem"; | ||
| 56 | |||
| 57 | public static readonly XName ConditionElement = WxsNamespace + "Condition"; | ||
| 58 | public static readonly XName PublishElement = WxsNamespace + "Publish"; | ||
| 59 | public static readonly XName CustomTableElement = WxsNamespace + "CustomTable"; | ||
| 60 | public static readonly XName ColumnElement = WxsNamespace + "Column"; | ||
| 61 | public static readonly XName RowElement = WxsNamespace + "Row"; | ||
| 62 | public static readonly XName DataElement = WxsNamespace + "Data"; | ||
| 63 | public static readonly XName CreateFolderElement = WxsNamespace + "CreateFolder"; | ||
| 64 | |||
| 65 | public static readonly XName CustomActionElement = WxsNamespace + "CustomAction"; | ||
| 66 | |||
| 67 | public static readonly XName ComponentSearchElement = WxsNamespace + "ComponentSearch"; | ||
| 68 | public static readonly XName ComponentElement = WxsNamespace + "Component"; | ||
| 69 | |||
| 70 | public static readonly XName LevelElement = WxsNamespace + "Level"; | ||
| 71 | public static readonly XName DialogElement = WxsNamespace + "Dialog"; | ||
| 72 | public static readonly XName StandardDirectoryElement = WxsNamespace + "StandardDirectory"; | ||
| 73 | public static readonly XName DirectoryElement = WxsNamespace + "Directory"; | ||
| 74 | public static readonly XName DirectorySearchElement = WxsNamespace + "DirectorySearch"; | ||
| 75 | public static readonly XName CopyFileElement = WxsNamespace + "CopyFile"; | ||
| 76 | public static readonly XName EnvironmentElement = WxsNamespace + "Environment"; | ||
| 77 | public static readonly XName ErrorElement = WxsNamespace + "Error"; | ||
| 78 | public static readonly XName SubscribeElement = WxsNamespace + "Subscribe"; | ||
| 79 | public static readonly XName ExtensionElement = WxsNamespace + "Extension"; | ||
| 80 | public static readonly XName ExternalFileElement = WxsNamespace + "ExternalFile"; | ||
| 81 | public static readonly XName SymbolPathElement = WxsNamespace + "SymbolPath"; | ||
| 82 | public static readonly XName IgnoreRangeElement = WxsNamespace + "IgnoreRange"; | ||
| 83 | |||
| 84 | public static readonly XName FeatureElement = WxsNamespace + "Feature"; | ||
| 85 | public static readonly XName ComponentRefElement = WxsNamespace + "ComponentRef"; | ||
| 86 | public static readonly XName SFPFileElement = WxsNamespace + "SFPFile"; | ||
| 87 | public static readonly XName IconElement = WxsNamespace + "Icon"; | ||
| 88 | public static readonly XName FamilyElement = WxsNamespace + "Family"; | ||
| 89 | public static readonly XName IniFileElement = WxsNamespace + "IniFile"; | ||
| 90 | public static readonly XName IniFileSearchElement = WxsNamespace + "IniFileSearch"; | ||
| 91 | public static readonly XName IsolateComponentElement = WxsNamespace + "IsolateComponent"; | ||
| 92 | public static readonly XName LaunchElement = WxsNamespace + "Launch"; | ||
| 93 | public static readonly XName ListBoxElement = WxsNamespace + "ListBox"; | ||
| 94 | public static readonly XName ListViewElement = WxsNamespace + "ListView"; | ||
| 95 | public static readonly XName PermissionElement = WxsNamespace + "Permission"; | ||
| 96 | public static readonly XName MediaElement = WxsNamespace + "Media"; | ||
| 97 | public static readonly XName MIMEElement = WxsNamespace + "MIME"; | ||
| 98 | public static readonly XName ConfigurationElement = WxsNamespace + "Configuration"; | ||
| 99 | public static readonly XName DependencyElement = WxsNamespace + "Dependency"; | ||
| 100 | public static readonly XName ExclusionElement = WxsNamespace + "Exclusion"; | ||
| 101 | public static readonly XName IgnoreTableElement = WxsNamespace + "IgnoreTable"; | ||
| 102 | public static readonly XName SubstitutionElement = WxsNamespace + "Substitution"; | ||
| 103 | public static readonly XName DigitalCertificateElement = WxsNamespace + "DigitalCertificate"; | ||
| 104 | public static readonly XName DigitalSignatureElement = WxsNamespace + "DigitalSignature"; | ||
| 105 | public static readonly XName EmbeddedChainerElement = WxsNamespace + "EmbeddedChainer"; | ||
| 106 | public static readonly XName EmbeddedUIElement = WxsNamespace + "EmbeddedUI"; | ||
| 107 | public static readonly XName EmbeddedUIResourceElement = WxsNamespace + "EmbeddedUIResource"; | ||
| 108 | public static readonly XName PermissionExElement = WxsNamespace + "PermissionEx"; | ||
| 109 | public static readonly XName PackageCertificatesElement = WxsNamespace + "PackageCertificates"; | ||
| 110 | public static readonly XName PatchCertificatesElement = WxsNamespace + "PatchCertificates"; | ||
| 111 | public static readonly XName ShortcutPropertyElement = WxsNamespace + "ShortcutProperty"; | ||
| 112 | public static readonly XName ODBCDataSourceElement = WxsNamespace + "ODBCDataSource"; | ||
| 113 | public static readonly XName ODBCDriverElement = WxsNamespace + "ODBCDriver"; | ||
| 114 | public static readonly XName ODBCTranslatorElement = WxsNamespace + "ODBCTranslator"; | ||
| 115 | public static readonly XName PatchMetadataElement = WxsNamespace + "PatchMetadata"; | ||
| 116 | public static readonly XName OptimizeCustomActionsElement = WxsNamespace + "OptimizeCustomActions"; | ||
| 117 | public static readonly XName CustomPropertyElement = WxsNamespace + "CustomProperty"; | ||
| 118 | public static readonly XName PatchSequenceElement = WxsNamespace + "PatchSequence"; | ||
| 119 | public static readonly XName ProgIdElement = WxsNamespace + "ProgId"; | ||
| 120 | public static readonly XName ReplacePatchElement = WxsNamespace + "ReplacePatch"; | ||
| 121 | public static readonly XName TargetProductCodeElement = WxsNamespace + "TargetProductCode"; | ||
| 122 | public static readonly XName PatchPropertyElement = WxsNamespace + "PatchProperty"; | ||
| 123 | public static readonly XName CategoryElement = WxsNamespace + "Category"; | ||
| 124 | public static readonly XName RadioButtonElement = WxsNamespace + "RadioButton"; | ||
| 125 | public static readonly XName RadioButtonGroupElement = WxsNamespace + "RadioButtonGroup"; | ||
| 126 | public static readonly XName RegistryKeyElement = WxsNamespace + "RegistryKey"; | ||
| 127 | public static readonly XName RegistryValueElement = WxsNamespace + "RegistryValue"; | ||
| 128 | public static readonly XName MultiStringElement = WxsNamespace + "MultiString"; | ||
| 129 | public static readonly XName RegistrySearchElement = WxsNamespace + "RegistrySearch"; | ||
| 130 | public static readonly XName RemoveFolderElement = WxsNamespace + "RemoveFolder"; | ||
| 131 | public static readonly XName RemoveFileElement = WxsNamespace + "RemoveFile"; | ||
| 132 | public static readonly XName RemoveRegistryKeyElement = WxsNamespace + "RemoveRegistryKey"; | ||
| 133 | public static readonly XName RemoveRegistryValueElement = WxsNamespace + "RemoveRegistryValue"; | ||
| 134 | public static readonly XName ReserveCostElement = WxsNamespace + "ReserveCost"; | ||
| 135 | public static readonly XName ServiceControlElement = WxsNamespace + "ServiceControl"; | ||
| 136 | public static readonly XName ServiceArgumentElement = WxsNamespace + "ServiceArgument"; | ||
| 137 | public static readonly XName ServiceInstallElement = WxsNamespace + "ServiceInstall"; | ||
| 138 | public static readonly XName ServiceDependencyElement = WxsNamespace + "ServiceDependency"; | ||
| 139 | public static readonly XName SFPCatalogElement = WxsNamespace + "SFPCatalog"; | ||
| 140 | public static readonly XName ShortcutElement = WxsNamespace + "Shortcut"; | ||
| 141 | public static readonly XName FileSearchElement = WxsNamespace + "FileSearch"; | ||
| 142 | public static readonly XName TargetFileElement = WxsNamespace + "TargetFile"; | ||
| 143 | public static readonly XName TargetImageElement = WxsNamespace + "TargetImage"; | ||
| 144 | public static readonly XName TextStyleElement = WxsNamespace + "TextStyle"; | ||
| 145 | public static readonly XName TypeLibElement = WxsNamespace + "TypeLib"; | ||
| 146 | public static readonly XName UpgradeElement = WxsNamespace + "Upgrade"; | ||
| 147 | public static readonly XName UpgradeVersionElement = WxsNamespace + "UpgradeVersion"; | ||
| 148 | public static readonly XName UpgradeFileElement = WxsNamespace + "UpgradeFile"; | ||
| 149 | public static readonly XName UpgradeImageElement = WxsNamespace + "UpgradeImage"; | ||
| 150 | public static readonly XName UITextElement = WxsNamespace + "UIText"; | ||
| 151 | public static readonly XName VerbElement = WxsNamespace + "Verb"; | ||
| 152 | public static readonly XName ComplianceCheckElement = WxsNamespace + "ComplianceCheck"; | ||
| 153 | public static readonly XName FileSearchRefElement = WxsNamespace + "FileSearchRef"; | ||
| 154 | public static readonly XName ComplianceDriveElement = WxsNamespace + "ComplianceDrive"; | ||
| 155 | public static readonly XName DirectorySearchRefElement = WxsNamespace + "DirectorySearchRef"; | ||
| 156 | public static readonly XName RegistrySearchRefElement = WxsNamespace + "RegistrySearchRef"; | ||
| 157 | public static readonly XName MajorUpgradeElement = WxsNamespace + "MajorUpgrade"; | ||
| 158 | //public static readonly XName Element = WxsNamespace + ""; | ||
| 159 | } | ||
| 160 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs new file mode 100644 index 00000000..304d0152 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs | |||
| @@ -0,0 +1,610 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | #if DELETE | ||
| 4 | |||
| 5 | namespace WixToolset.Core.WindowsInstaller | ||
| 6 | { | ||
| 7 | using System; | ||
| 8 | using System.Collections; | ||
| 9 | using System.Collections.Generic; | ||
| 10 | using System.Globalization; | ||
| 11 | using WixToolset.Core.WindowsInstaller.Msi; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Data.WindowsInstaller; | ||
| 15 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 16 | using WixToolset.Extensibility; | ||
| 17 | using WixToolset.Extensibility.Services; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Creates a transform by diffing two outputs. | ||
| 21 | /// </summary> | ||
| 22 | public sealed class Differ | ||
| 23 | { | ||
| 24 | private readonly List<IInspectorExtension> inspectorExtensions; | ||
| 25 | private bool showPedanticMessages; | ||
| 26 | private bool suppressKeepingSpecialRows; | ||
| 27 | private bool preserveUnchangedRows; | ||
| 28 | private const char sectionDelimiter = '/'; | ||
| 29 | private readonly IMessaging messaging; | ||
| 30 | private SummaryInformationStreams transformSummaryInfo; | ||
| 31 | |||
| 32 | /// <summary> | ||
| 33 | /// Instantiates a new Differ class. | ||
| 34 | /// </summary> | ||
| 35 | public Differ(IMessaging messaging) | ||
| 36 | { | ||
| 37 | this.inspectorExtensions = new List<IInspectorExtension>(); | ||
| 38 | this.messaging = messaging; | ||
| 39 | } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets or sets the option to show pedantic messages. | ||
| 43 | /// </summary> | ||
| 44 | /// <value>The option to show pedantic messages.</value> | ||
| 45 | public bool ShowPedanticMessages | ||
| 46 | { | ||
| 47 | get { return this.showPedanticMessages; } | ||
| 48 | set { this.showPedanticMessages = value; } | ||
| 49 | } | ||
| 50 | |||
| 51 | /// <summary> | ||
| 52 | /// Gets or sets the option to suppress keeping special rows. | ||
| 53 | /// </summary> | ||
| 54 | /// <value>The option to suppress keeping special rows.</value> | ||
| 55 | public bool SuppressKeepingSpecialRows | ||
| 56 | { | ||
| 57 | get { return this.suppressKeepingSpecialRows; } | ||
| 58 | set { this.suppressKeepingSpecialRows = value; } | ||
| 59 | } | ||
| 60 | |||
| 61 | /// <summary> | ||
| 62 | /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. | ||
| 63 | /// </summary> | ||
| 64 | /// <value>The option to keep all rows including unchanged rows.</value> | ||
| 65 | public bool PreserveUnchangedRows | ||
| 66 | { | ||
| 67 | get { return this.preserveUnchangedRows; } | ||
| 68 | set { this.preserveUnchangedRows = value; } | ||
| 69 | } | ||
| 70 | |||
| 71 | /// <summary> | ||
| 72 | /// Adds an extension. | ||
| 73 | /// </summary> | ||
| 74 | /// <param name="extension">The extension to add.</param> | ||
| 75 | public void AddExtension(IInspectorExtension extension) | ||
| 76 | { | ||
| 77 | this.inspectorExtensions.Add(extension); | ||
| 78 | } | ||
| 79 | |||
| 80 | /// <summary> | ||
| 81 | /// Creates a transform by diffing two outputs. | ||
| 82 | /// </summary> | ||
| 83 | /// <param name="targetOutput">The target output.</param> | ||
| 84 | /// <param name="updatedOutput">The updated output.</param> | ||
| 85 | /// <returns>The transform.</returns> | ||
| 86 | public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput) | ||
| 87 | { | ||
| 88 | return this.Diff(targetOutput, updatedOutput, 0); | ||
| 89 | } | ||
| 90 | |||
| 91 | /// <summary> | ||
| 92 | /// Creates a transform by diffing two outputs. | ||
| 93 | /// </summary> | ||
| 94 | /// <param name="targetOutput">The target output.</param> | ||
| 95 | /// <param name="updatedOutput">The updated output.</param> | ||
| 96 | /// <param name="validationFlags"></param> | ||
| 97 | /// <returns>The transform.</returns> | ||
| 98 | public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, TransformFlags validationFlags) | ||
| 99 | { | ||
| 100 | WindowsInstallerData transform = new WindowsInstallerData(null); | ||
| 101 | transform.Type = OutputType.Transform; | ||
| 102 | transform.Codepage = updatedOutput.Codepage; | ||
| 103 | this.transformSummaryInfo = new SummaryInformationStreams(); | ||
| 104 | |||
| 105 | // compare the codepages | ||
| 106 | if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) | ||
| 107 | { | ||
| 108 | this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); | ||
| 109 | if (null != updatedOutput.SourceLineNumbers) | ||
| 110 | { | ||
| 111 | this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | // compare the output types | ||
| 116 | if (targetOutput.Type != updatedOutput.Type) | ||
| 117 | { | ||
| 118 | throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); | ||
| 119 | } | ||
| 120 | |||
| 121 | // compare the contents of the tables | ||
| 122 | foreach (Table targetTable in targetOutput.Tables) | ||
| 123 | { | ||
| 124 | Table updatedTable = updatedOutput.Tables[targetTable.Name]; | ||
| 125 | TableOperation operation = TableOperation.None; | ||
| 126 | |||
| 127 | List<Row> rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); | ||
| 128 | |||
| 129 | if (TableOperation.Drop == operation) | ||
| 130 | { | ||
| 131 | Table droppedTable = transform.EnsureTable(targetTable.Definition); | ||
| 132 | droppedTable.Operation = TableOperation.Drop; | ||
| 133 | } | ||
| 134 | else if (TableOperation.None == operation) | ||
| 135 | { | ||
| 136 | Table modified = transform.EnsureTable(updatedTable.Definition); | ||
| 137 | rows.ForEach(r => modified.Rows.Add(r)); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | // added tables | ||
| 142 | foreach (Table updatedTable in updatedOutput.Tables) | ||
| 143 | { | ||
| 144 | if (null == targetOutput.Tables[updatedTable.Name]) | ||
| 145 | { | ||
| 146 | Table addedTable = transform.EnsureTable(updatedTable.Definition); | ||
| 147 | addedTable.Operation = TableOperation.Add; | ||
| 148 | |||
| 149 | foreach (Row updatedRow in updatedTable.Rows) | ||
| 150 | { | ||
| 151 | updatedRow.Operation = RowOperation.Add; | ||
| 152 | updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; | ||
| 153 | addedTable.Rows.Add(updatedRow); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | // set summary information properties | ||
| 159 | if (!this.suppressKeepingSpecialRows) | ||
| 160 | { | ||
| 161 | Table summaryInfoTable = transform.Tables["_SummaryInformation"]; | ||
| 162 | this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); | ||
| 163 | } | ||
| 164 | |||
| 165 | return transform; | ||
| 166 | } | ||
| 167 | |||
| 168 | /// <summary> | ||
| 169 | /// Add a row to the <paramref name="index"/> using the primary key. | ||
| 170 | /// </summary> | ||
| 171 | /// <param name="index">The indexed rows.</param> | ||
| 172 | /// <param name="row">The row to index.</param> | ||
| 173 | private void AddIndexedRow(IDictionary index, Row row) | ||
| 174 | { | ||
| 175 | string primaryKey = row.GetPrimaryKey('/'); | ||
| 176 | if (null != primaryKey) | ||
| 177 | { | ||
| 178 | // Overriding WixActionRows have a primary key defined and take precedence in the index. | ||
| 179 | if (row is WixActionRow) | ||
| 180 | { | ||
| 181 | WixActionRow currentRow = (WixActionRow)row; | ||
| 182 | if (index.Contains(primaryKey)) | ||
| 183 | { | ||
| 184 | // If the current row is not overridable, see if the indexed row is. | ||
| 185 | if (!currentRow.Overridable) | ||
| 186 | { | ||
| 187 | WixActionRow indexedRow = index[primaryKey] as WixActionRow; | ||
| 188 | if (null != indexedRow && indexedRow.Overridable) | ||
| 189 | { | ||
| 190 | // The indexed key is overridable and should be replaced | ||
| 191 | // (not removed and re-added which results in two Array.Copy | ||
| 192 | // operations for SortedList, or may be re-hashing in other | ||
| 193 | // implementations of IDictionary). | ||
| 194 | index[primaryKey] = currentRow; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | // If we got this far, the row does not need to be indexed. | ||
| 199 | return; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | // Nothing else should be added more than once. | ||
| 204 | if (!index.Contains(primaryKey)) | ||
| 205 | { | ||
| 206 | index.Add(primaryKey, row); | ||
| 207 | } | ||
| 208 | else if (this.showPedanticMessages) | ||
| 209 | { | ||
| 210 | this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); | ||
| 211 | } | ||
| 212 | } | ||
| 213 | else // use the string representation of the row as its primary key (it may not be unique) | ||
| 214 | { | ||
| 215 | // this is provided for compatibility with unreal tables with no primary key | ||
| 216 | // all real tables must specify at least one column as the primary key | ||
| 217 | primaryKey = row.ToString(); | ||
| 218 | index[primaryKey] = row; | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow) | ||
| 223 | { | ||
| 224 | Row comparedRow = null; | ||
| 225 | keepRow = false; | ||
| 226 | operation = RowOperation.None; | ||
| 227 | |||
| 228 | if (null == targetRow ^ null == updatedRow) | ||
| 229 | { | ||
| 230 | if (null == targetRow) | ||
| 231 | { | ||
| 232 | operation = updatedRow.Operation = RowOperation.Add; | ||
| 233 | comparedRow = updatedRow; | ||
| 234 | } | ||
| 235 | else if (null == updatedRow) | ||
| 236 | { | ||
| 237 | operation = targetRow.Operation = RowOperation.Delete; | ||
| 238 | targetRow.SectionId = targetRow.SectionId + sectionDelimiter; | ||
| 239 | comparedRow = targetRow; | ||
| 240 | keepRow = true; | ||
| 241 | } | ||
| 242 | } | ||
| 243 | else // possibly modified | ||
| 244 | { | ||
| 245 | updatedRow.Operation = RowOperation.None; | ||
| 246 | if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) | ||
| 247 | { | ||
| 248 | // ignore rows that shouldn't be in a transform | ||
| 249 | if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) | ||
| 250 | { | ||
| 251 | updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; | ||
| 252 | comparedRow = updatedRow; | ||
| 253 | keepRow = true; | ||
| 254 | operation = RowOperation.Modify; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | else | ||
| 258 | { | ||
| 259 | if (this.preserveUnchangedRows) | ||
| 260 | { | ||
| 261 | keepRow = true; | ||
| 262 | } | ||
| 263 | |||
| 264 | for (int i = 0; i < updatedRow.Fields.Length; i++) | ||
| 265 | { | ||
| 266 | ColumnDefinition columnDefinition = updatedRow.Fields[i].Column; | ||
| 267 | |||
| 268 | if (!columnDefinition.PrimaryKey) | ||
| 269 | { | ||
| 270 | bool modified = false; | ||
| 271 | |||
| 272 | if (i >= targetRow.Fields.Length) | ||
| 273 | { | ||
| 274 | columnDefinition.Added = true; | ||
| 275 | modified = true; | ||
| 276 | } | ||
| 277 | else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) | ||
| 278 | { | ||
| 279 | if (null == targetRow[i] ^ null == updatedRow[i]) | ||
| 280 | { | ||
| 281 | modified = true; | ||
| 282 | } | ||
| 283 | else if (null != targetRow[i] && null != updatedRow[i]) | ||
| 284 | { | ||
| 285 | modified = ((int)targetRow[i] != (int)updatedRow[i]); | ||
| 286 | } | ||
| 287 | } | ||
| 288 | else if (ColumnType.Preserved == columnDefinition.Type) | ||
| 289 | { | ||
| 290 | updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data; | ||
| 291 | |||
| 292 | // keep rows containing preserved fields so the historical data is available to the binder | ||
| 293 | keepRow = !this.suppressKeepingSpecialRows; | ||
| 294 | } | ||
| 295 | else if (ColumnType.Object == columnDefinition.Type) | ||
| 296 | { | ||
| 297 | ObjectField targetObjectField = (ObjectField)targetRow.Fields[i]; | ||
| 298 | ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i]; | ||
| 299 | |||
| 300 | updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; | ||
| 301 | updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; | ||
| 302 | |||
| 303 | // always keep a copy of the previous data even if they are identical | ||
| 304 | // This makes diff.wixmst clean and easier to control patch logic | ||
| 305 | updatedObjectField.PreviousData = (string)targetObjectField.Data; | ||
| 306 | |||
| 307 | // always remember the unresolved data for target build | ||
| 308 | updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData; | ||
| 309 | |||
| 310 | // keep rows containing object fields so the files can be compared in the binder | ||
| 311 | keepRow = !this.suppressKeepingSpecialRows; | ||
| 312 | } | ||
| 313 | else | ||
| 314 | { | ||
| 315 | modified = ((string)targetRow[i] != (string)updatedRow[i]); | ||
| 316 | } | ||
| 317 | |||
| 318 | if (modified) | ||
| 319 | { | ||
| 320 | if (null != updatedRow.Fields[i].PreviousData) | ||
| 321 | { | ||
| 322 | updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString(); | ||
| 323 | } | ||
| 324 | |||
| 325 | updatedRow.Fields[i].Modified = true; | ||
| 326 | operation = updatedRow.Operation = RowOperation.Modify; | ||
| 327 | keepRow = true; | ||
| 328 | } | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | if (keepRow) | ||
| 333 | { | ||
| 334 | comparedRow = updatedRow; | ||
| 335 | comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; | ||
| 336 | } | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 340 | return comparedRow; | ||
| 341 | } | ||
| 342 | |||
| 343 | private List<Row> CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) | ||
| 344 | { | ||
| 345 | List<Row> rows = new List<Row>(); | ||
| 346 | operation = TableOperation.None; | ||
| 347 | |||
| 348 | // dropped tables | ||
| 349 | if (null == updatedTable ^ null == targetTable) | ||
| 350 | { | ||
| 351 | if (null == targetTable) | ||
| 352 | { | ||
| 353 | operation = TableOperation.Add; | ||
| 354 | rows.AddRange(updatedTable.Rows); | ||
| 355 | } | ||
| 356 | else if (null == updatedTable) | ||
| 357 | { | ||
| 358 | operation = TableOperation.Drop; | ||
| 359 | } | ||
| 360 | } | ||
| 361 | else // possibly modified tables | ||
| 362 | { | ||
| 363 | SortedList updatedPrimaryKeys = new SortedList(); | ||
| 364 | SortedList targetPrimaryKeys = new SortedList(); | ||
| 365 | |||
| 366 | // compare the table definitions | ||
| 367 | if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) | ||
| 368 | { | ||
| 369 | // continue to the next table; may be more mismatches | ||
| 370 | this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); | ||
| 371 | } | ||
| 372 | else | ||
| 373 | { | ||
| 374 | this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); | ||
| 375 | |||
| 376 | // diff the target and updated rows | ||
| 377 | foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys) | ||
| 378 | { | ||
| 379 | string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key; | ||
| 380 | bool keepRow = false; | ||
| 381 | RowOperation rowOperation = RowOperation.None; | ||
| 382 | |||
| 383 | Row compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value as Row, updatedPrimaryKeys[targetPrimaryKey] as Row, out rowOperation, out keepRow); | ||
| 384 | |||
| 385 | if (keepRow) | ||
| 386 | { | ||
| 387 | rows.Add(compared); | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 391 | // find the inserted rows | ||
| 392 | foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys) | ||
| 393 | { | ||
| 394 | string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key; | ||
| 395 | |||
| 396 | if (!targetPrimaryKeys.Contains(updatedPrimaryKey)) | ||
| 397 | { | ||
| 398 | Row updatedRow = (Row)updatedPrimaryKeyEntry.Value; | ||
| 399 | |||
| 400 | updatedRow.Operation = RowOperation.Add; | ||
| 401 | updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; | ||
| 402 | rows.Add(updatedRow); | ||
| 403 | } | ||
| 404 | } | ||
| 405 | } | ||
| 406 | } | ||
| 407 | |||
| 408 | return rows; | ||
| 409 | } | ||
| 410 | |||
| 411 | private void IndexPrimaryKeys(Table targetTable, SortedList targetPrimaryKeys, Table updatedTable, SortedList updatedPrimaryKeys) | ||
| 412 | { | ||
| 413 | // index the target rows | ||
| 414 | foreach (Row row in targetTable.Rows) | ||
| 415 | { | ||
| 416 | this.AddIndexedRow(targetPrimaryKeys, row); | ||
| 417 | |||
| 418 | if ("Property" == targetTable.Name) | ||
| 419 | { | ||
| 420 | if ("ProductCode" == (string)row[0]) | ||
| 421 | { | ||
| 422 | this.transformSummaryInfo.TargetProductCode = (string)row[1]; | ||
| 423 | if ("*" == this.transformSummaryInfo.TargetProductCode) | ||
| 424 | { | ||
| 425 | this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); | ||
| 426 | } | ||
| 427 | } | ||
| 428 | else if ("ProductVersion" == (string)row[0]) | ||
| 429 | { | ||
| 430 | this.transformSummaryInfo.TargetProductVersion = (string)row[1]; | ||
| 431 | } | ||
| 432 | else if ("UpgradeCode" == (string)row[0]) | ||
| 433 | { | ||
| 434 | this.transformSummaryInfo.TargetUpgradeCode = (string)row[1]; | ||
| 435 | } | ||
| 436 | } | ||
| 437 | else if ("_SummaryInformation" == targetTable.Name) | ||
| 438 | { | ||
| 439 | if (1 == (int)row[0]) // PID_CODEPAGE | ||
| 440 | { | ||
| 441 | this.transformSummaryInfo.TargetSummaryInfoCodepage = (string)row[1]; | ||
| 442 | } | ||
| 443 | else if (7 == (int)row[0]) // PID_TEMPLATE | ||
| 444 | { | ||
| 445 | this.transformSummaryInfo.TargetPlatformAndLanguage = (string)row[1]; | ||
| 446 | } | ||
| 447 | else if (14 == (int)row[0]) // PID_PAGECOUNT | ||
| 448 | { | ||
| 449 | this.transformSummaryInfo.TargetMinimumVersion = (string)row[1]; | ||
| 450 | } | ||
| 451 | } | ||
| 452 | } | ||
| 453 | |||
| 454 | // index the updated rows | ||
| 455 | foreach (Row row in updatedTable.Rows) | ||
| 456 | { | ||
| 457 | this.AddIndexedRow(updatedPrimaryKeys, row); | ||
| 458 | |||
| 459 | if ("Property" == updatedTable.Name) | ||
| 460 | { | ||
| 461 | if ("ProductCode" == (string)row[0]) | ||
| 462 | { | ||
| 463 | this.transformSummaryInfo.UpdatedProductCode = (string)row[1]; | ||
| 464 | if ("*" == this.transformSummaryInfo.UpdatedProductCode) | ||
| 465 | { | ||
| 466 | this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); | ||
| 467 | } | ||
| 468 | } | ||
| 469 | else if ("ProductVersion" == (string)row[0]) | ||
| 470 | { | ||
| 471 | this.transformSummaryInfo.UpdatedProductVersion = (string)row[1]; | ||
| 472 | } | ||
| 473 | } | ||
| 474 | else if ("_SummaryInformation" == updatedTable.Name) | ||
| 475 | { | ||
| 476 | if (1 == (int)row[0]) // PID_CODEPAGE | ||
| 477 | { | ||
| 478 | this.transformSummaryInfo.UpdatedSummaryInfoCodepage = (string)row[1]; | ||
| 479 | } | ||
| 480 | else if (7 == (int)row[0]) // PID_TEMPLATE | ||
| 481 | { | ||
| 482 | this.transformSummaryInfo.UpdatedPlatformAndLanguage = (string)row[1]; | ||
| 483 | } | ||
| 484 | else if (14 == (int)row[0]) // PID_PAGECOUNT | ||
| 485 | { | ||
| 486 | this.transformSummaryInfo.UpdatedMinimumVersion = (string)row[1]; | ||
| 487 | } | ||
| 488 | } | ||
| 489 | } | ||
| 490 | } | ||
| 491 | |||
| 492 | private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) | ||
| 493 | { | ||
| 494 | // calculate the minimum version of MSI required to process the transform | ||
| 495 | int targetMin; | ||
| 496 | int updatedMin; | ||
| 497 | int minimumVersion = 100; | ||
| 498 | |||
| 499 | if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin)) | ||
| 500 | { | ||
| 501 | minimumVersion = Math.Max(targetMin, updatedMin); | ||
| 502 | } | ||
| 503 | |||
| 504 | Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count); | ||
| 505 | foreach (Row row in summaryInfoTable.Rows) | ||
| 506 | { | ||
| 507 | summaryRows[row[0]] = row; | ||
| 508 | |||
| 509 | if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) | ||
| 510 | { | ||
| 511 | row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; | ||
| 512 | row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; | ||
| 513 | } | ||
| 514 | else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0]) | ||
| 515 | { | ||
| 516 | row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; | ||
| 517 | } | ||
| 518 | else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
| 519 | { | ||
| 520 | row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; | ||
| 521 | } | ||
| 522 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
| 523 | { | ||
| 524 | row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); | ||
| 525 | } | ||
| 526 | else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0]) | ||
| 527 | { | ||
| 528 | row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); | ||
| 529 | } | ||
| 530 | else if ((int)SummaryInformation.Transform.Security == (int)row[0]) | ||
| 531 | { | ||
| 532 | row[1] = "4"; | ||
| 533 | } | ||
| 534 | } | ||
| 535 | |||
| 536 | if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) | ||
| 537 | { | ||
| 538 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
| 539 | summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; | ||
| 540 | summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; | ||
| 541 | } | ||
| 542 | |||
| 543 | if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) | ||
| 544 | { | ||
| 545 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
| 546 | summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; | ||
| 547 | summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; | ||
| 548 | } | ||
| 549 | |||
| 550 | if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) | ||
| 551 | { | ||
| 552 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
| 553 | summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; | ||
| 554 | summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); | ||
| 555 | } | ||
| 556 | |||
| 557 | if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement)) | ||
| 558 | { | ||
| 559 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
| 560 | summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; | ||
| 561 | summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); | ||
| 562 | } | ||
| 563 | |||
| 564 | if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) | ||
| 565 | { | ||
| 566 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
| 567 | summaryRow[0] = (int)SummaryInformation.Transform.Security; | ||
| 568 | summaryRow[1] = "4"; | ||
| 569 | } | ||
| 570 | } | ||
| 571 | |||
| 572 | private class SummaryInformationStreams | ||
| 573 | { | ||
| 574 | public string TargetSummaryInfoCodepage | ||
| 575 | { get; set; } | ||
| 576 | |||
| 577 | public string TargetPlatformAndLanguage | ||
| 578 | { get; set; } | ||
| 579 | |||
| 580 | public string TargetProductCode | ||
| 581 | { get; set; } | ||
| 582 | |||
| 583 | public string TargetProductVersion | ||
| 584 | { get; set; } | ||
| 585 | |||
| 586 | public string TargetUpgradeCode | ||
| 587 | { get; set; } | ||
| 588 | |||
| 589 | public string TargetMinimumVersion | ||
| 590 | { get; set; } | ||
| 591 | |||
| 592 | public string UpdatedSummaryInfoCodepage | ||
| 593 | { get; set; } | ||
| 594 | |||
| 595 | public string UpdatedPlatformAndLanguage | ||
| 596 | { get; set; } | ||
| 597 | |||
| 598 | public string UpdatedProductCode | ||
| 599 | { get; set; } | ||
| 600 | |||
| 601 | public string UpdatedProductVersion | ||
| 602 | { get; set; } | ||
| 603 | |||
| 604 | public string UpdatedMinimumVersion | ||
| 605 | { get; set; } | ||
| 606 | } | ||
| 607 | } | ||
| 608 | } | ||
| 609 | |||
| 610 | #endif | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs new file mode 100644 index 00000000..8305b5e6 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs | |||
| @@ -0,0 +1,121 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Data.WindowsInstaller; | ||
| 11 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 12 | using WixToolset.Extensibility.Data; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | internal class WindowsInstallerBackendHelper : IWindowsInstallerBackendHelper | ||
| 16 | { | ||
| 17 | private readonly IBackendHelper backendHelper; | ||
| 18 | |||
| 19 | public WindowsInstallerBackendHelper(IServiceProvider serviceProvider) | ||
| 20 | { | ||
| 21 | this.backendHelper = serviceProvider.GetService<IBackendHelper>(); | ||
| 22 | } | ||
| 23 | |||
| 24 | #region IBackendHelper interfaces | ||
| 25 | |||
| 26 | public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); | ||
| 27 | |||
| 28 | public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); | ||
| 29 | |||
| 30 | public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); | ||
| 31 | |||
| 32 | public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); | ||
| 33 | |||
| 34 | public string CreateGuid() => this.backendHelper.CreateGuid(); | ||
| 35 | |||
| 36 | public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); | ||
| 37 | |||
| 38 | public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); | ||
| 39 | |||
| 40 | public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); | ||
| 41 | |||
| 42 | public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); | ||
| 43 | |||
| 44 | public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); | ||
| 45 | |||
| 46 | public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); | ||
| 47 | |||
| 48 | public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); | ||
| 49 | |||
| 50 | public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); | ||
| 51 | |||
| 52 | public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); | ||
| 53 | |||
| 54 | public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); | ||
| 55 | |||
| 56 | public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); | ||
| 57 | |||
| 58 | public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); | ||
| 59 | |||
| 60 | public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); | ||
| 61 | |||
| 62 | public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); | ||
| 63 | |||
| 64 | public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); | ||
| 65 | |||
| 66 | #endregion | ||
| 67 | |||
| 68 | #region IWindowsInstallerBackendHelper interfaces | ||
| 69 | |||
| 70 | public Row CreateRow(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinition tableDefinition) | ||
| 71 | { | ||
| 72 | var table = data.EnsureTable(tableDefinition); | ||
| 73 | |||
| 74 | var row = table.CreateRow(symbol.SourceLineNumbers); | ||
| 75 | row.SectionId = section.Id; | ||
| 76 | |||
| 77 | return row; | ||
| 78 | } | ||
| 79 | |||
| 80 | public bool TryAddSymbolToMatchingTableDefinitions(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinitionCollection tableDefinitions) | ||
| 81 | { | ||
| 82 | var tableDefinition = tableDefinitions.FirstOrDefault(t => t.SymbolDefinition?.Name == symbol.Definition.Name); | ||
| 83 | if (tableDefinition == null) | ||
| 84 | { | ||
| 85 | return false; | ||
| 86 | } | ||
| 87 | |||
| 88 | var row = this.CreateRow(section, symbol, data, tableDefinition); | ||
| 89 | var rowOffset = 0; | ||
| 90 | |||
| 91 | if (tableDefinition.SymbolIdIsPrimaryKey) | ||
| 92 | { | ||
| 93 | row[0] = symbol.Id.Id; | ||
| 94 | rowOffset = 1; | ||
| 95 | } | ||
| 96 | |||
| 97 | for (var i = 0; i < symbol.Fields.Length; ++i) | ||
| 98 | { | ||
| 99 | if (i < tableDefinition.Columns.Length) | ||
| 100 | { | ||
| 101 | var column = tableDefinition.Columns[i + rowOffset]; | ||
| 102 | |||
| 103 | switch (column.Type) | ||
| 104 | { | ||
| 105 | case ColumnType.Number: | ||
| 106 | row[i + rowOffset] = column.Nullable ? symbol.AsNullableNumber(i) : symbol.AsNumber(i); | ||
| 107 | break; | ||
| 108 | |||
| 109 | default: | ||
| 110 | row[i + rowOffset] = symbol.AsString(i); | ||
| 111 | break; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | return true; | ||
| 117 | } | ||
| 118 | |||
| 119 | #endregion | ||
| 120 | } | ||
| 121 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs new file mode 100644 index 00000000..57f2f753 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs | |||
| @@ -0,0 +1,272 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Inscribe | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Runtime.InteropServices; | ||
| 10 | using System.Security.Cryptography.X509Certificates; | ||
| 11 | using WixToolset.Core.Native.Msi; | ||
| 12 | using WixToolset.Core.WindowsInstaller.Bind; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.WindowsInstaller; | ||
| 15 | using WixToolset.Extensibility.Data; | ||
| 16 | using WixToolset.Extensibility.Services; | ||
| 17 | |||
| 18 | internal class InscribeMsiPackageCommand | ||
| 19 | { | ||
| 20 | public InscribeMsiPackageCommand(IInscribeContext context) | ||
| 21 | { | ||
| 22 | this.Context = context; | ||
| 23 | this.Messaging = context.ServiceProvider.GetService<IMessaging>(); | ||
| 24 | this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService<IWindowsInstallerBackendHelper>(); | ||
| 25 | this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); | ||
| 26 | } | ||
| 27 | |||
| 28 | private IInscribeContext Context { get; } | ||
| 29 | |||
| 30 | private IMessaging Messaging { get; } | ||
| 31 | |||
| 32 | private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } | ||
| 33 | |||
| 34 | private TableDefinitionCollection TableDefinitions { get; } | ||
| 35 | |||
| 36 | public bool Execute() | ||
| 37 | { | ||
| 38 | // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered | ||
| 39 | var foundUnsignedExternals = false; | ||
| 40 | var shouldCommit = false; | ||
| 41 | |||
| 42 | var attributes = File.GetAttributes(this.Context.InputFilePath); | ||
| 43 | if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) | ||
| 44 | { | ||
| 45 | this.Messaging.Write(ErrorMessages.ReadOnlyOutputFile(this.Context.InputFilePath)); | ||
| 46 | return shouldCommit; | ||
| 47 | } | ||
| 48 | |||
| 49 | using (var database = new Database(this.Context.InputFilePath, OpenDatabase.Transact)) | ||
| 50 | { | ||
| 51 | // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content | ||
| 52 | var codepage = 1252; | ||
| 53 | |||
| 54 | // list of certificates for this database (hash/identifier) | ||
| 55 | var certificates = new Dictionary<string, string>(); | ||
| 56 | |||
| 57 | // Reset the in-memory tables for this new database | ||
| 58 | var digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]); | ||
| 59 | var digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]); | ||
| 60 | |||
| 61 | // If any digital signature records exist that are not of the media type, preserve them | ||
| 62 | if (database.TableExists("MsiDigitalSignature")) | ||
| 63 | { | ||
| 64 | using (var digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) | ||
| 65 | { | ||
| 66 | foreach (var digitalSignatureRecord in digitalSignatureView.Records) | ||
| 67 | { | ||
| 68 | Row digitalSignatureRow = null; | ||
| 69 | digitalSignatureRow = digitalSignatureTable.CreateRow(null); | ||
| 70 | |||
| 71 | var table = digitalSignatureRecord.GetString(0); | ||
| 72 | var signObject = digitalSignatureRecord.GetString(1); | ||
| 73 | |||
| 74 | digitalSignatureRow[0] = table; | ||
| 75 | digitalSignatureRow[1] = signObject; | ||
| 76 | digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); | ||
| 77 | |||
| 78 | if (false == digitalSignatureRecord.IsNull(3)) | ||
| 79 | { | ||
| 80 | // Export to a file, because the MSI API's require us to provide a file path on disk | ||
| 81 | var hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature"); | ||
| 82 | var hashFileName = String.Concat(table, ".", signObject, ".bin"); | ||
| 83 | |||
| 84 | Directory.CreateDirectory(hashPath); | ||
| 85 | hashPath = Path.Combine(hashPath, hashFileName); | ||
| 86 | |||
| 87 | using (var fs = File.Create(hashPath)) | ||
| 88 | { | ||
| 89 | int bytesRead; | ||
| 90 | var buffer = new byte[1024 * 4]; | ||
| 91 | |||
| 92 | while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) | ||
| 93 | { | ||
| 94 | fs.Write(buffer, 0, bytesRead); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | digitalSignatureRow[3] = hashFileName; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | // If any digital certificates exist, extract and preserve them | ||
| 105 | if (database.TableExists("MsiDigitalCertificate")) | ||
| 106 | { | ||
| 107 | using (var digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) | ||
| 108 | { | ||
| 109 | foreach (var digitalCertificateRecord in digitalCertificateView.Records) | ||
| 110 | { | ||
| 111 | var certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate | ||
| 112 | |||
| 113 | // Export to a file, because the MSI API's require us to provide a file path on disk | ||
| 114 | var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); | ||
| 115 | Directory.CreateDirectory(certPath); | ||
| 116 | certPath = Path.Combine(certPath, String.Concat(certificateId, ".cer")); | ||
| 117 | |||
| 118 | using (var fs = File.Create(certPath)) | ||
| 119 | { | ||
| 120 | int bytesRead; | ||
| 121 | var buffer = new byte[1024 * 4]; | ||
| 122 | |||
| 123 | while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) | ||
| 124 | { | ||
| 125 | fs.Write(buffer, 0, bytesRead); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | // Add it to our "add to MsiDigitalCertificate" table dictionary | ||
| 130 | var digitalCertificateRow = digitalCertificateTable.CreateRow(null); | ||
| 131 | digitalCertificateRow[0] = certificateId; | ||
| 132 | |||
| 133 | // Now set the file path on disk where this binary stream will be picked up at import time | ||
| 134 | digitalCertificateRow[1] = String.Concat(certificateId, ".cer"); | ||
| 135 | |||
| 136 | // Load the cert to get it's thumbprint | ||
| 137 | var cert = X509Certificate.CreateFromCertFile(certPath); | ||
| 138 | var cert2 = new X509Certificate2(cert); | ||
| 139 | |||
| 140 | certificates.Add(cert2.Thumbprint, certificateId); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | using (var mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) | ||
| 146 | { | ||
| 147 | foreach (var mediaRecord in mediaView.Records) | ||
| 148 | { | ||
| 149 | X509Certificate2 cert2 = null; | ||
| 150 | Row digitalSignatureRow = null; | ||
| 151 | |||
| 152 | var cabName = mediaRecord.GetString(4); // get the name of the cab | ||
| 153 | // If there is no cabinet or it's an internal cab, skip it. | ||
| 154 | if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) | ||
| 155 | { | ||
| 156 | continue; | ||
| 157 | } | ||
| 158 | |||
| 159 | var cabId = mediaRecord.GetString(1); // get the ID of the cab | ||
| 160 | var cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName); | ||
| 161 | |||
| 162 | // If the cabs aren't there, throw an error but continue to catch the other errors | ||
| 163 | if (!File.Exists(cabPath)) | ||
| 164 | { | ||
| 165 | this.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath)); | ||
| 166 | continue; | ||
| 167 | } | ||
| 168 | |||
| 169 | try | ||
| 170 | { | ||
| 171 | // Get the certificate from the cab | ||
| 172 | var signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); | ||
| 173 | cert2 = new X509Certificate2(signedFileCert); | ||
| 174 | } | ||
| 175 | catch (System.Security.Cryptography.CryptographicException e) | ||
| 176 | { | ||
| 177 | var HResult = unchecked((uint)Marshal.GetHRForException(e)); | ||
| 178 | |||
| 179 | // If the file has no cert, continue, but flag that we found at least one so we can later give a warning | ||
| 180 | if (0x80092009 == HResult) // CRYPT_E_NO_MATCH | ||
| 181 | { | ||
| 182 | foundUnsignedExternals = true; | ||
| 183 | continue; | ||
| 184 | } | ||
| 185 | |||
| 186 | // todo: exactly which HRESULT corresponds to this issue? | ||
| 187 | // If it's one of these exact platforms, warn the user that it may be due to their OS. | ||
| 188 | if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 | ||
| 189 | (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP | ||
| 190 | { | ||
| 191 | this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); | ||
| 192 | } | ||
| 193 | else // otherwise, generic error | ||
| 194 | { | ||
| 195 | this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added | ||
| 200 | if (!certificates.ContainsKey(cert2.Thumbprint)) | ||
| 201 | { | ||
| 202 | // generate a stable identifier | ||
| 203 | var certificateGeneratedId = this.WindowsInstallerBackendHelper.GenerateIdentifier("cer", cert2.Thumbprint); | ||
| 204 | |||
| 205 | // Add it to our "add to MsiDigitalCertificate" table dictionary | ||
| 206 | var digitalCertificateRow = digitalCertificateTable.CreateRow(null); | ||
| 207 | digitalCertificateRow[0] = certificateGeneratedId; | ||
| 208 | |||
| 209 | // Export to a file, because the MSI API's require us to provide a file path on disk | ||
| 210 | var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); | ||
| 211 | Directory.CreateDirectory(certPath); | ||
| 212 | certPath = Path.Combine(certPath, String.Concat(cert2.Thumbprint, ".cer")); | ||
| 213 | File.Delete(certPath); | ||
| 214 | |||
| 215 | using (var writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) | ||
| 216 | { | ||
| 217 | writer.Write(cert2.RawData); | ||
| 218 | writer.Close(); | ||
| 219 | } | ||
| 220 | |||
| 221 | // Now set the file path on disk where this binary stream will be picked up at import time | ||
| 222 | digitalCertificateRow[1] = String.Concat(cert2.Thumbprint, ".cer"); | ||
| 223 | |||
| 224 | certificates.Add(cert2.Thumbprint, certificateGeneratedId); | ||
| 225 | } | ||
| 226 | |||
| 227 | digitalSignatureRow = digitalSignatureTable.CreateRow(null); | ||
| 228 | |||
| 229 | digitalSignatureRow[0] = "Media"; | ||
| 230 | digitalSignatureRow[1] = cabId; | ||
| 231 | digitalSignatureRow[2] = certificates[cert2.Thumbprint]; | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | if (digitalCertificateTable.Rows.Count > 0) | ||
| 236 | { | ||
| 237 | var command = new CreateIdtFileCommand(this.Messaging, digitalCertificateTable, codepage, this.Context.IntermediateFolder, true); | ||
| 238 | command.Execute(); | ||
| 239 | |||
| 240 | database.Import(command.IdtPath); | ||
| 241 | shouldCommit = true; | ||
| 242 | } | ||
| 243 | |||
| 244 | if (digitalSignatureTable.Rows.Count > 0) | ||
| 245 | { | ||
| 246 | var command = new CreateIdtFileCommand(this.Messaging, digitalSignatureTable, codepage, this.Context.IntermediateFolder, true); | ||
| 247 | command.Execute(); | ||
| 248 | |||
| 249 | database.Import(command.IdtPath); | ||
| 250 | shouldCommit = true; | ||
| 251 | } | ||
| 252 | |||
| 253 | // TODO: if we created the table(s), then we should add the _Validation records for them. | ||
| 254 | |||
| 255 | certificates = null; | ||
| 256 | |||
| 257 | // If we did find external cabs but not all of them were signed, give a warning | ||
| 258 | if (foundUnsignedExternals) | ||
| 259 | { | ||
| 260 | this.Messaging.Write(WarningMessages.ExternalCabsAreNotSigned(this.Context.InputFilePath)); | ||
| 261 | } | ||
| 262 | |||
| 263 | if (shouldCommit) | ||
| 264 | { | ||
| 265 | database.Commit(); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | return shouldCommit; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Melter.cs b/src/wix/WixToolset.Core.WindowsInstaller/Melter.cs new file mode 100644 index 00000000..29e19e49 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Melter.cs | |||
| @@ -0,0 +1,399 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.CodeDom.Compiler; | ||
| 7 | using System.Collections; | ||
| 8 | using System.Collections.Generic; | ||
| 9 | using System.Collections.Specialized; | ||
| 10 | using System.Globalization; | ||
| 11 | using System.IO; | ||
| 12 | using System.Text; | ||
| 13 | using System.Text.RegularExpressions; | ||
| 14 | using WixToolset.Data; | ||
| 15 | |||
| 16 | /// <summary> | ||
| 17 | /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. | ||
| 18 | /// </summary> | ||
| 19 | public sealed class Melter | ||
| 20 | { | ||
| 21 | #if TODO_MELT | ||
| 22 | private MelterCore core; | ||
| 23 | private Decompiler decompiler; | ||
| 24 | |||
| 25 | private Wix.ComponentGroup componentGroup; | ||
| 26 | private Wix.DirectoryRef primaryDirectoryRef; | ||
| 27 | private Wix.Fragment fragment; | ||
| 28 | |||
| 29 | private string id; | ||
| 30 | private string moduleId; | ||
| 31 | private const string nullGuid = "{00000000-0000-0000-0000-000000000000}"; | ||
| 32 | |||
| 33 | public string Id | ||
| 34 | { | ||
| 35 | get { return this.id; } | ||
| 36 | set { this.id = value; } | ||
| 37 | } | ||
| 38 | |||
| 39 | public Decompiler Decompiler | ||
| 40 | { | ||
| 41 | get { return this.decompiler; } | ||
| 42 | set { this.decompiler = value; } | ||
| 43 | } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Creates a new melter object. | ||
| 47 | /// </summary> | ||
| 48 | /// <param name="decompiler">The decompiler to use during the melting process.</param> | ||
| 49 | /// <param name="id">The Id to use for the ComponentGroup, DirectoryRef, and WixVariables. If null, defaults to the Module's Id</param> | ||
| 50 | public Melter(Decompiler decompiler, string id) | ||
| 51 | { | ||
| 52 | this.core = new MelterCore(); | ||
| 53 | |||
| 54 | this.componentGroup = new Wix.ComponentGroup(); | ||
| 55 | this.fragment = new Wix.Fragment(); | ||
| 56 | this.primaryDirectoryRef = new Wix.DirectoryRef(); | ||
| 57 | |||
| 58 | this.decompiler = decompiler; | ||
| 59 | this.id = id; | ||
| 60 | |||
| 61 | if (null == this.decompiler) | ||
| 62 | { | ||
| 63 | this.core.OnMessage(WixErrors.ExpectedDecompiler("The melting process")); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | /// <summary> | ||
| 68 | /// Converts a Module wixout into a ComponentGroup. | ||
| 69 | /// </summary> | ||
| 70 | /// <param name="wixout">The output object representing the unbound merge module to melt.</param> | ||
| 71 | /// <returns>The converted Module as a ComponentGroup.</returns> | ||
| 72 | public Wix.Wix Melt(Output wixout) | ||
| 73 | { | ||
| 74 | this.moduleId = GetModuleId(wixout); | ||
| 75 | |||
| 76 | // Assign the default componentGroupId if none was specified | ||
| 77 | if (null == this.id) | ||
| 78 | { | ||
| 79 | this.id = this.moduleId; | ||
| 80 | } | ||
| 81 | |||
| 82 | this.componentGroup.Id = this.id; | ||
| 83 | this.primaryDirectoryRef.Id = this.id; | ||
| 84 | |||
| 85 | PreDecompile(wixout); | ||
| 86 | |||
| 87 | wixout.Type = OutputType.Product; | ||
| 88 | this.decompiler.TreatProductAsModule = true; | ||
| 89 | Wix.Wix wix = this.decompiler.Decompile(wixout); | ||
| 90 | |||
| 91 | if (null == wix) | ||
| 92 | { | ||
| 93 | return wix; | ||
| 94 | } | ||
| 95 | |||
| 96 | ConvertModule(wix); | ||
| 97 | |||
| 98 | return wix; | ||
| 99 | } | ||
| 100 | |||
| 101 | /// <summary> | ||
| 102 | /// Converts a Module to a ComponentGroup and adds all of its relevant elements to the main fragment. | ||
| 103 | /// </summary> | ||
| 104 | /// <param name="wix">The output object representing an unbound merge module.</param> | ||
| 105 | private void ConvertModule(Wix.Wix wix) | ||
| 106 | { | ||
| 107 | Wix.Product product = Melter.GetProduct(wix); | ||
| 108 | |||
| 109 | List<string> customActionsRemoved = new List<string>(); | ||
| 110 | Dictionary<Wix.Custom, Wix.InstallExecuteSequence> customsToRemove = new Dictionary<Wix.Custom, Wix.InstallExecuteSequence>(); | ||
| 111 | |||
| 112 | foreach (Wix.ISchemaElement child in product.Children) | ||
| 113 | { | ||
| 114 | Wix.Directory childDir = child as Wix.Directory; | ||
| 115 | if (null != childDir) | ||
| 116 | { | ||
| 117 | bool isTargetDir = this.WalkDirectory(childDir); | ||
| 118 | if (isTargetDir) | ||
| 119 | { | ||
| 120 | continue; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | else | ||
| 124 | { | ||
| 125 | Wix.Dependency childDep = child as Wix.Dependency; | ||
| 126 | if (null != childDep) | ||
| 127 | { | ||
| 128 | this.AddPropertyRef(childDep.RequiredId); | ||
| 129 | continue; | ||
| 130 | } | ||
| 131 | else if (child is Wix.Package) | ||
| 132 | { | ||
| 133 | continue; | ||
| 134 | } | ||
| 135 | else if (child is Wix.CustomAction) | ||
| 136 | { | ||
| 137 | Wix.CustomAction customAction = child as Wix.CustomAction; | ||
| 138 | string directoryId; | ||
| 139 | if (StartsWithStandardDirectoryId(customAction.Id, out directoryId) && customAction.Property == customAction.Id) | ||
| 140 | { | ||
| 141 | customActionsRemoved.Add(customAction.Id); | ||
| 142 | continue; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | else if (child is Wix.InstallExecuteSequence) | ||
| 146 | { | ||
| 147 | Wix.InstallExecuteSequence installExecuteSequence = child as Wix.InstallExecuteSequence; | ||
| 148 | |||
| 149 | foreach (Wix.ISchemaElement sequenceChild in installExecuteSequence.Children) | ||
| 150 | { | ||
| 151 | Wix.Custom custom = sequenceChild as Wix.Custom; | ||
| 152 | string directoryId; | ||
| 153 | if (custom != null && StartsWithStandardDirectoryId(custom.Action, out directoryId)) | ||
| 154 | { | ||
| 155 | customsToRemove.Add(custom, installExecuteSequence); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | this.fragment.AddChild(child); | ||
| 162 | } | ||
| 163 | |||
| 164 | // For any customaction that we removed, also remove the scheduling of that action. | ||
| 165 | foreach (Wix.Custom custom in customsToRemove.Keys) | ||
| 166 | { | ||
| 167 | if (customActionsRemoved.Contains(custom.Action)) | ||
| 168 | { | ||
| 169 | ((Wix.InstallExecuteSequence)customsToRemove[custom]).RemoveChild(custom); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | AddProperty(this.moduleId, this.id); | ||
| 174 | |||
| 175 | wix.RemoveChild(product); | ||
| 176 | wix.AddChild(this.fragment); | ||
| 177 | |||
| 178 | this.fragment.AddChild(this.componentGroup); | ||
| 179 | this.fragment.AddChild(this.primaryDirectoryRef); | ||
| 180 | } | ||
| 181 | |||
| 182 | /// <summary> | ||
| 183 | /// Gets the module from the Wix object. | ||
| 184 | /// </summary> | ||
| 185 | /// <param name="wix">The Wix object.</param> | ||
| 186 | /// <returns>The Module in the Wix object, null if no Module was found</returns> | ||
| 187 | private static Wix.Product GetProduct(Wix.Wix wix) | ||
| 188 | { | ||
| 189 | foreach (Wix.ISchemaElement element in wix.Children) | ||
| 190 | { | ||
| 191 | Wix.Product productElement = element as Wix.Product; | ||
| 192 | if (null != productElement) | ||
| 193 | { | ||
| 194 | return productElement; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | return null; | ||
| 198 | } | ||
| 199 | |||
| 200 | /// <summary> | ||
| 201 | /// Adds a PropertyRef to the main Fragment. | ||
| 202 | /// </summary> | ||
| 203 | /// <param name="propertyRefId">Id of the PropertyRef.</param> | ||
| 204 | private void AddPropertyRef(string propertyRefId) | ||
| 205 | { | ||
| 206 | Wix.PropertyRef propertyRef = new Wix.PropertyRef(); | ||
| 207 | propertyRef.Id = propertyRefId; | ||
| 208 | this.fragment.AddChild(propertyRef); | ||
| 209 | } | ||
| 210 | |||
| 211 | /// <summary> | ||
| 212 | /// Adds a Property to the main Fragment. | ||
| 213 | /// </summary> | ||
| 214 | /// <param name="propertyId">Id of the Property.</param> | ||
| 215 | /// <param name="value">Value of the Property.</param> | ||
| 216 | private void AddProperty(string propertyId, string value) | ||
| 217 | { | ||
| 218 | Wix.Property property = new Wix.Property(); | ||
| 219 | property.Id = propertyId; | ||
| 220 | property.Value = value; | ||
| 221 | this.fragment.AddChild(property); | ||
| 222 | } | ||
| 223 | |||
| 224 | /// <summary> | ||
| 225 | /// Walks a directory structure obtaining Component Id's and Standard Directory Id's. | ||
| 226 | /// </summary> | ||
| 227 | /// <param name="directory">The Directory to walk.</param> | ||
| 228 | /// <returns>true if the directory is TARGETDIR.</returns> | ||
| 229 | private bool WalkDirectory(Wix.Directory directory) | ||
| 230 | { | ||
| 231 | bool isTargetDir = false; | ||
| 232 | if ("TARGETDIR" == directory.Id) | ||
| 233 | { | ||
| 234 | isTargetDir = true; | ||
| 235 | } | ||
| 236 | |||
| 237 | string standardDirectoryId = null; | ||
| 238 | if (Melter.StartsWithStandardDirectoryId(directory.Id, out standardDirectoryId) && !isTargetDir) | ||
| 239 | { | ||
| 240 | this.AddSetPropertyCustomAction(directory.Id, String.Format(CultureInfo.InvariantCulture, "[{0}]", standardDirectoryId)); | ||
| 241 | } | ||
| 242 | |||
| 243 | foreach (Wix.ISchemaElement child in directory.Children) | ||
| 244 | { | ||
| 245 | Wix.Directory childDir = child as Wix.Directory; | ||
| 246 | if (null != childDir) | ||
| 247 | { | ||
| 248 | if (isTargetDir) | ||
| 249 | { | ||
| 250 | this.primaryDirectoryRef.AddChild(child); | ||
| 251 | } | ||
| 252 | this.WalkDirectory(childDir); | ||
| 253 | } | ||
| 254 | else | ||
| 255 | { | ||
| 256 | Wix.Component childComponent = child as Wix.Component; | ||
| 257 | if (null != childComponent) | ||
| 258 | { | ||
| 259 | if (isTargetDir) | ||
| 260 | { | ||
| 261 | this.primaryDirectoryRef.AddChild(child); | ||
| 262 | } | ||
| 263 | this.AddComponentRef(childComponent); | ||
| 264 | } | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | return isTargetDir; | ||
| 269 | } | ||
| 270 | |||
| 271 | /// <summary> | ||
| 272 | /// Gets the module Id out of the Output object. | ||
| 273 | /// </summary> | ||
| 274 | /// <param name="wixout">The output object.</param> | ||
| 275 | /// <returns>The module Id from the Output object.</returns> | ||
| 276 | private string GetModuleId(Output wixout) | ||
| 277 | { | ||
| 278 | // get the moduleId from the wixout | ||
| 279 | Table moduleSignatureTable = wixout.Tables["ModuleSignature"]; | ||
| 280 | if (null == moduleSignatureTable || 0 >= moduleSignatureTable.Rows.Count) | ||
| 281 | { | ||
| 282 | this.core.OnMessage(WixErrors.ExpectedTableInMergeModule("ModuleSignature")); | ||
| 283 | } | ||
| 284 | return moduleSignatureTable.Rows[0].Fields[0].Data.ToString(); | ||
| 285 | } | ||
| 286 | |||
| 287 | /// <summary> | ||
| 288 | /// Determines if the directory Id starts with a standard directory id. | ||
| 289 | /// </summary> | ||
| 290 | /// <param name="directoryId">The directory id.</param> | ||
| 291 | /// <param name="standardDirectoryId">The standard directory id.</param> | ||
| 292 | /// <returns>true if the directory starts with a standard directory id.</returns> | ||
| 293 | private static bool StartsWithStandardDirectoryId(string directoryId, out string standardDirectoryId) | ||
| 294 | { | ||
| 295 | standardDirectoryId = null; | ||
| 296 | foreach (string id in WindowsInstallerStandard.GetStandardDirectories()) | ||
| 297 | { | ||
| 298 | if (directoryId.StartsWith(id, StringComparison.Ordinal)) | ||
| 299 | { | ||
| 300 | standardDirectoryId = id; | ||
| 301 | return true; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | return false; | ||
| 305 | } | ||
| 306 | |||
| 307 | /// <summary> | ||
| 308 | /// Adds a ComponentRef to the main ComponentGroup. | ||
| 309 | /// </summary> | ||
| 310 | /// <param name="component">The component to add.</param> | ||
| 311 | private void AddComponentRef(Wix.Component component) | ||
| 312 | { | ||
| 313 | Wix.ComponentRef componentRef = new Wix.ComponentRef(); | ||
| 314 | componentRef.Id = component.Id; | ||
| 315 | this.componentGroup.AddChild(componentRef); | ||
| 316 | } | ||
| 317 | |||
| 318 | /// <summary> | ||
| 319 | /// Adds a SetProperty CA for a Directory. | ||
| 320 | /// </summary> | ||
| 321 | /// <param name="propertyId">The Id of the Property to set.</param> | ||
| 322 | /// <param name="value">The value to set the Property to.</param> | ||
| 323 | private void AddSetPropertyCustomAction(string propertyId, string value) | ||
| 324 | { | ||
| 325 | // Add the action | ||
| 326 | Wix.CustomAction customAction = new Wix.CustomAction(); | ||
| 327 | customAction.Id = propertyId; | ||
| 328 | customAction.Property = propertyId; | ||
| 329 | customAction.Value = value; | ||
| 330 | this.fragment.AddChild(customAction); | ||
| 331 | |||
| 332 | // Schedule the action | ||
| 333 | Wix.InstallExecuteSequence installExecuteSequence = new Wix.InstallExecuteSequence(); | ||
| 334 | Wix.Custom custom = new Wix.Custom(); | ||
| 335 | custom.Action = customAction.Id; | ||
| 336 | custom.Before = "CostInitialize"; | ||
| 337 | installExecuteSequence.AddChild(custom); | ||
| 338 | this.fragment.AddChild(installExecuteSequence); | ||
| 339 | } | ||
| 340 | |||
| 341 | /// <summary> | ||
| 342 | /// Does any operations to the wixout that would need to be done before decompiling. | ||
| 343 | /// </summary> | ||
| 344 | /// <param name="wixout">The output object representing the unbound merge module.</param> | ||
| 345 | private void PreDecompile(Output wixout) | ||
| 346 | { | ||
| 347 | string wixVariable = String.Format(CultureInfo.InvariantCulture, "!(wix.{0}", this.id); | ||
| 348 | |||
| 349 | foreach (Table table in wixout.Tables) | ||
| 350 | { | ||
| 351 | // Determine if the table has a feature foreign key | ||
| 352 | bool hasFeatureForeignKey = false; | ||
| 353 | foreach (ColumnDefinition columnDef in table.Definition.Columns) | ||
| 354 | { | ||
| 355 | if (null != columnDef.KeyTable) | ||
| 356 | { | ||
| 357 | string[] keyTables = columnDef.KeyTable.Split(';'); | ||
| 358 | foreach (string keyTable in keyTables) | ||
| 359 | { | ||
| 360 | if ("Feature" == keyTable) | ||
| 361 | { | ||
| 362 | hasFeatureForeignKey = true; | ||
| 363 | break; | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | // If this table has no foreign keys to the feature table, skip it. | ||
| 370 | if (!hasFeatureForeignKey) | ||
| 371 | { | ||
| 372 | continue; | ||
| 373 | } | ||
| 374 | |||
| 375 | // Go through all the rows and replace the null guid with the wix variable | ||
| 376 | // for columns that are foreign keys into the feature table. | ||
| 377 | foreach (Row row in table.Rows) | ||
| 378 | { | ||
| 379 | foreach (Field field in row.Fields) | ||
| 380 | { | ||
| 381 | if (null != field.Column.KeyTable) | ||
| 382 | { | ||
| 383 | string[] keyTables = field.Column.KeyTable.Split(';'); | ||
| 384 | foreach (string keyTable in keyTables) | ||
| 385 | { | ||
| 386 | if ("Feature" == keyTable) | ||
| 387 | { | ||
| 388 | field.Data = field.Data.ToString().Replace(nullGuid, wixVariable); | ||
| 389 | break; | ||
| 390 | } | ||
| 391 | } | ||
| 392 | } | ||
| 393 | } | ||
| 394 | } | ||
| 395 | } | ||
| 396 | } | ||
| 397 | #endif | ||
| 398 | } | ||
| 399 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs b/src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs new file mode 100644 index 00000000..034c9465 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | /// <summary> | ||
| 8 | /// Melts a Module Wix document into a ComponentGroup representation. | ||
| 9 | /// </summary> | ||
| 10 | public sealed class MelterCore | ||
| 11 | { | ||
| 12 | #if TODO_MELT | ||
| 13 | /// <summary> | ||
| 14 | /// Gets whether the melter core encountered an error while processing. | ||
| 15 | /// </summary> | ||
| 16 | /// <value>Flag if core encountered an error during processing.</value> | ||
| 17 | public bool EncounteredError | ||
| 18 | { | ||
| 19 | get { return Messaging.Instance.EncounteredError; } | ||
| 20 | } | ||
| 21 | |||
| 22 | /// <summary> | ||
| 23 | /// Sends a message to the message delegate if there is one. | ||
| 24 | /// </summary> | ||
| 25 | /// <param name="mea">Message event arguments.</param> | ||
| 26 | public void OnMessage(MessageEventArgs e) | ||
| 27 | { | ||
| 28 | Messaging.Instance.OnMessage(e); | ||
| 29 | } | ||
| 30 | #endif | ||
| 31 | } | ||
| 32 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs new file mode 100644 index 00000000..3bd58c25 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs | |||
| @@ -0,0 +1,85 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using WixToolset.Core.WindowsInstaller.Bind; | ||
| 6 | using WixToolset.Core.WindowsInstaller.Decompile; | ||
| 7 | using WixToolset.Core.WindowsInstaller.Inscribe; | ||
| 8 | using WixToolset.Core.WindowsInstaller.Unbind; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class MsiBackend : IBackend | ||
| 15 | { | ||
| 16 | public IBindResult Bind(IBindContext context) | ||
| 17 | { | ||
| 18 | var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); | ||
| 19 | |||
| 20 | var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendBinderExtension>(); | ||
| 21 | |||
| 22 | foreach (var extension in backendExtensions) | ||
| 23 | { | ||
| 24 | extension.PreBackendBind(context); | ||
| 25 | } | ||
| 26 | |||
| 27 | IBindResult result = null; | ||
| 28 | var dispose = true; | ||
| 29 | try | ||
| 30 | { | ||
| 31 | var command = new BindDatabaseCommand(context, backendExtensions, "darice.cub"); | ||
| 32 | result = command.Execute(); | ||
| 33 | |||
| 34 | foreach (var extension in backendExtensions) | ||
| 35 | { | ||
| 36 | extension.PostBackendBind(result); | ||
| 37 | } | ||
| 38 | |||
| 39 | dispose = false; | ||
| 40 | return result; | ||
| 41 | } | ||
| 42 | finally | ||
| 43 | { | ||
| 44 | if (dispose) | ||
| 45 | { | ||
| 46 | result?.Dispose(); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | public IDecompileResult Decompile(IDecompileContext context) | ||
| 52 | { | ||
| 53 | var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); | ||
| 54 | |||
| 55 | var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendDecompilerExtension>(); | ||
| 56 | |||
| 57 | foreach (var extension in backendExtensions) | ||
| 58 | { | ||
| 59 | extension.PreBackendDecompile(context); | ||
| 60 | } | ||
| 61 | |||
| 62 | var command = new DecompileMsiOrMsmCommand(context, backendExtensions); | ||
| 63 | var result = command.Execute(); | ||
| 64 | |||
| 65 | foreach (var extension in backendExtensions) | ||
| 66 | { | ||
| 67 | extension.PostBackendDecompile(result); | ||
| 68 | } | ||
| 69 | |||
| 70 | return result; | ||
| 71 | } | ||
| 72 | |||
| 73 | public bool Inscribe(IInscribeContext context) | ||
| 74 | { | ||
| 75 | var command = new InscribeMsiPackageCommand(context); | ||
| 76 | return command.Execute(); | ||
| 77 | } | ||
| 78 | |||
| 79 | public Intermediate Unbind(IUnbindContext context) | ||
| 80 | { | ||
| 81 | var command = new UnbindMsiOrMsmCommand(context); | ||
| 82 | return command.Execute(); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs new file mode 100644 index 00000000..4927ee8c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using WixToolset.Core.WindowsInstaller.Bind; | ||
| 6 | using WixToolset.Core.WindowsInstaller.Decompile; | ||
| 7 | using WixToolset.Core.WindowsInstaller.Unbind; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class MsmBackend : IBackend | ||
| 14 | { | ||
| 15 | public IBindResult Bind(IBindContext context) | ||
| 16 | { | ||
| 17 | var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); | ||
| 18 | |||
| 19 | var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendBinderExtension>(); | ||
| 20 | |||
| 21 | foreach (var extension in backendExtensions) | ||
| 22 | { | ||
| 23 | extension.PreBackendBind(context); | ||
| 24 | } | ||
| 25 | |||
| 26 | IBindResult result = null; | ||
| 27 | try | ||
| 28 | { | ||
| 29 | var command = new BindDatabaseCommand(context, backendExtensions, "mergemod.cub"); | ||
| 30 | result = command.Execute(); | ||
| 31 | |||
| 32 | foreach (var extension in backendExtensions) | ||
| 33 | { | ||
| 34 | extension.PostBackendBind(result); | ||
| 35 | } | ||
| 36 | |||
| 37 | return result; | ||
| 38 | } | ||
| 39 | catch | ||
| 40 | { | ||
| 41 | result?.Dispose(); | ||
| 42 | throw; | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | public IDecompileResult Decompile(IDecompileContext context) | ||
| 47 | { | ||
| 48 | var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); | ||
| 49 | |||
| 50 | var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendDecompilerExtension>(); | ||
| 51 | |||
| 52 | foreach (var extension in backendExtensions) | ||
| 53 | { | ||
| 54 | extension.PreBackendDecompile(context); | ||
| 55 | } | ||
| 56 | |||
| 57 | var command = new DecompileMsiOrMsmCommand(context, backendExtensions); | ||
| 58 | var result = command.Execute(); | ||
| 59 | |||
| 60 | foreach (var extension in backendExtensions) | ||
| 61 | { | ||
| 62 | extension.PostBackendDecompile(result); | ||
| 63 | } | ||
| 64 | |||
| 65 | return result; | ||
| 66 | } | ||
| 67 | |||
| 68 | public bool Inscribe(IInscribeContext context) => false; | ||
| 69 | |||
| 70 | public Intermediate Unbind(IUnbindContext context) | ||
| 71 | { | ||
| 72 | var command = new UnbindMsiOrMsmCommand(context); | ||
| 73 | return command.Execute(); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs new file mode 100644 index 00000000..c46b6027 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs | |||
| @@ -0,0 +1,162 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Core.WindowsInstaller.Bind; | ||
| 10 | using WixToolset.Core.Native.Msi; | ||
| 11 | using WixToolset.Core.WindowsInstaller.Unbind; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Data.WindowsInstaller; | ||
| 15 | using WixToolset.Extensibility; | ||
| 16 | using WixToolset.Extensibility.Data; | ||
| 17 | using WixToolset.Extensibility.Services; | ||
| 18 | |||
| 19 | internal class MspBackend : IBackend | ||
| 20 | { | ||
| 21 | public IBindResult Bind(IBindContext context) | ||
| 22 | { | ||
| 23 | var messaging = context.ServiceProvider.GetService<IMessaging>(); | ||
| 24 | |||
| 25 | var backendHelper = context.ServiceProvider.GetService<IBackendHelper>(); | ||
| 26 | |||
| 27 | var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); | ||
| 28 | |||
| 29 | var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendBinderExtension>(); | ||
| 30 | |||
| 31 | foreach (var extension in backendExtensions) | ||
| 32 | { | ||
| 33 | extension.PreBackendBind(context); | ||
| 34 | } | ||
| 35 | |||
| 36 | // Create transforms named in patch transforms. | ||
| 37 | IEnumerable<PatchTransform> patchTransforms; | ||
| 38 | { | ||
| 39 | var command = new CreatePatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, context.IntermediateFolder); | ||
| 40 | patchTransforms = command.Execute(); | ||
| 41 | } | ||
| 42 | |||
| 43 | // Enhance the intermediate by attaching the created patch transforms. | ||
| 44 | IEnumerable<SubStorage> subStorages; | ||
| 45 | { | ||
| 46 | var command = new AttachPatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms); | ||
| 47 | subStorages = command.Execute(); | ||
| 48 | } | ||
| 49 | |||
| 50 | // Create WindowsInstallerData with patch metdata and transforms as sub-storages | ||
| 51 | // Create MSP from WindowsInstallerData | ||
| 52 | IBindResult result = null; | ||
| 53 | try | ||
| 54 | { | ||
| 55 | var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null); | ||
| 56 | result = command.Execute(); | ||
| 57 | |||
| 58 | foreach (var extension in backendExtensions) | ||
| 59 | { | ||
| 60 | extension.PostBackendBind(result); | ||
| 61 | } | ||
| 62 | |||
| 63 | return result; | ||
| 64 | } | ||
| 65 | catch | ||
| 66 | { | ||
| 67 | result?.Dispose(); | ||
| 68 | throw; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException(); | ||
| 73 | |||
| 74 | public bool Inscribe(IInscribeContext context) => throw new NotImplementedException(); | ||
| 75 | |||
| 76 | public Intermediate Unbind(IUnbindContext context) | ||
| 77 | { | ||
| 78 | #if TODO_PATCHING | ||
| 79 | Output patch; | ||
| 80 | |||
| 81 | // patch files are essentially database files (use a special flag to let the API know its a patch file) | ||
| 82 | try | ||
| 83 | { | ||
| 84 | using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) | ||
| 85 | { | ||
| 86 | var unbindCommand = new UnbindDatabaseCommand(context.Messaging, database, context.InputFilePath, OutputType.Patch, context.ExportBasePath, context.IntermediateFolder, context.IsAdminImage, context.SuppressDemodularization, skipSummaryInfo: false); | ||
| 87 | patch = unbindCommand.Execute(); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | catch (Win32Exception e) | ||
| 91 | { | ||
| 92 | if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED | ||
| 93 | { | ||
| 94 | throw new WixException(WixErrors.OpenDatabaseFailed(context.InputFilePath)); | ||
| 95 | } | ||
| 96 | |||
| 97 | throw; | ||
| 98 | } | ||
| 99 | |||
| 100 | // retrieve the transforms (they are in substorages) | ||
| 101 | using (Storage storage = Storage.Open(context.InputFilePath, StorageMode.Read | StorageMode.ShareDenyWrite)) | ||
| 102 | { | ||
| 103 | Table summaryInformationTable = patch.Tables["_SummaryInformation"]; | ||
| 104 | foreach (Row row in summaryInformationTable.Rows) | ||
| 105 | { | ||
| 106 | if (8 == (int)row[0]) // PID_LASTAUTHOR | ||
| 107 | { | ||
| 108 | string value = (string)row[1]; | ||
| 109 | |||
| 110 | foreach (string decoratedSubStorageName in value.Split(';')) | ||
| 111 | { | ||
| 112 | string subStorageName = decoratedSubStorageName.Substring(1); | ||
| 113 | string transformFile = Path.Combine(context.IntermediateFolder, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst")); | ||
| 114 | |||
| 115 | // ensure the parent directory exists | ||
| 116 | Directory.CreateDirectory(Path.GetDirectoryName(transformFile)); | ||
| 117 | |||
| 118 | // copy the substorage to a new storage for the transform file | ||
| 119 | using (Storage subStorage = storage.OpenStorage(subStorageName)) | ||
| 120 | { | ||
| 121 | using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create)) | ||
| 122 | { | ||
| 123 | subStorage.CopyTo(transformStorage); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | // unbind the transform | ||
| 128 | var unbindCommand= new UnbindTransformCommand(context.Messaging, transformFile, (null == context.ExportBasePath ? null : Path.Combine(context.ExportBasePath, subStorageName)), context.IntermediateFolder); | ||
| 129 | var transform = unbindCommand.Execute(); | ||
| 130 | |||
| 131 | patch.SubStorages.Add(new SubStorage(subStorageName, transform)); | ||
| 132 | } | ||
| 133 | |||
| 134 | break; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | // extract the files from the cabinets | ||
| 140 | // TODO: use per-transform export paths for support of multi-product patches | ||
| 141 | if (null != context.ExportBasePath && !context.SuppressExtractCabinets) | ||
| 142 | { | ||
| 143 | using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) | ||
| 144 | { | ||
| 145 | foreach (SubStorage subStorage in patch.SubStorages) | ||
| 146 | { | ||
| 147 | // only patch transforms should carry files | ||
| 148 | if (subStorage.Name.StartsWith("#", StringComparison.Ordinal)) | ||
| 149 | { | ||
| 150 | var extractCommand = new ExtractCabinetsCommand(subStorage.Data, database, context.InputFilePath, context.ExportBasePath, context.IntermediateFolder); | ||
| 151 | extractCommand.Execute(); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | return patch; | ||
| 158 | #endif | ||
| 159 | throw new NotImplementedException(); | ||
| 160 | } | ||
| 161 | } | ||
| 162 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs new file mode 100644 index 00000000..a6d86c10 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Core.WindowsInstaller.Unbind; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Extensibility; | ||
| 9 | using WixToolset.Extensibility.Data; | ||
| 10 | |||
| 11 | internal class MstBackend : IBackend | ||
| 12 | { | ||
| 13 | public IBindResult Bind(IBindContext context) | ||
| 14 | { | ||
| 15 | #if TODO_PATCHING | ||
| 16 | var command = new BindTransformCommand(); | ||
| 17 | command.Extensions = context.Extensions; | ||
| 18 | command.TempFilesLocation = context.IntermediateFolder; | ||
| 19 | command.Transform = context.IntermediateRepresentation; | ||
| 20 | command.OutputPath = context.OutputPath; | ||
| 21 | command.Execute(); | ||
| 22 | |||
| 23 | return new BindResult(Array.Empty<FileTransfer>(), Array.Empty<string>()); | ||
| 24 | #endif | ||
| 25 | throw new NotImplementedException(); | ||
| 26 | } | ||
| 27 | |||
| 28 | public IDecompileResult Decompile(IDecompileContext context) | ||
| 29 | { | ||
| 30 | throw new NotImplementedException(); | ||
| 31 | } | ||
| 32 | |||
| 33 | public bool Inscribe(IInscribeContext context) | ||
| 34 | { | ||
| 35 | throw new NotImplementedException(); | ||
| 36 | } | ||
| 37 | |||
| 38 | public Intermediate Unbind(IUnbindContext context) | ||
| 39 | { | ||
| 40 | var command = new UnbindMsiOrMsmCommand(context); | ||
| 41 | return command.Execute(); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } \ No newline at end of file | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs b/src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs new file mode 100644 index 00000000..ad7764bc --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data.WindowsInstaller; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// A dictionary of rows. Unlike the RowIndexedList this | ||
| 11 | /// will throw when multiple rows with the same key are added. | ||
| 12 | /// </summary> | ||
| 13 | internal sealed class RowDictionary<T> : Dictionary<string, T> where T : Row | ||
| 14 | { | ||
| 15 | /// <summary> | ||
| 16 | /// Creates an empty <see cref="RowDictionary{T}"/>. | ||
| 17 | /// </summary> | ||
| 18 | public RowDictionary() | ||
| 19 | : base(StringComparer.InvariantCulture) | ||
| 20 | { | ||
| 21 | } | ||
| 22 | |||
| 23 | /// <summary> | ||
| 24 | /// Creates and populates a <see cref="RowDictionary{T}"/> with the rows from the given <see cref="Table"/>. | ||
| 25 | /// </summary> | ||
| 26 | /// <param name="table">The table to index.</param> | ||
| 27 | /// <remarks> | ||
| 28 | /// Rows added to the index are not automatically added to the given <paramref name="table"/>. | ||
| 29 | /// </remarks> | ||
| 30 | public RowDictionary(Table table) | ||
| 31 | : this() | ||
| 32 | { | ||
| 33 | if (null != table) | ||
| 34 | { | ||
| 35 | foreach (T row in table.Rows) | ||
| 36 | { | ||
| 37 | this.Add(row); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | /// <summary> | ||
| 43 | /// Adds a row to the dictionary using the row key. | ||
| 44 | /// </summary> | ||
| 45 | /// <param name="row">Row to add to the dictionary.</param> | ||
| 46 | public void Add(T row) | ||
| 47 | { | ||
| 48 | this.Add(row.GetKey(), row); | ||
| 49 | } | ||
| 50 | |||
| 51 | /// <summary> | ||
| 52 | /// Gets the row by integer key. | ||
| 53 | /// </summary> | ||
| 54 | /// <param name="key">Integer key to look up.</param> | ||
| 55 | /// <returns>Row or null if key is not found.</returns> | ||
| 56 | public T Get(int key) | ||
| 57 | { | ||
| 58 | return this.Get(key.ToString()); | ||
| 59 | } | ||
| 60 | |||
| 61 | /// <summary> | ||
| 62 | /// Gets the row by string key. | ||
| 63 | /// </summary> | ||
| 64 | /// <param name="key">String key to look up.</param> | ||
| 65 | /// <returns>Row or null if key is not found.</returns> | ||
| 66 | public T Get(string key) | ||
| 67 | { | ||
| 68 | return this.TryGetValue(key, out var result) ? result : null; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs new file mode 100644 index 00000000..8f52bed9 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs | |||
| @@ -0,0 +1,147 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Unbind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using WixToolset.Core.Native; | ||
| 12 | using WixToolset.Core.Native.Msi; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.WindowsInstaller; | ||
| 15 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 16 | |||
| 17 | internal class ExtractCabinetsCommand | ||
| 18 | { | ||
| 19 | public ExtractCabinetsCommand(WindowsInstallerData output, Database database, string inputFilePath, string exportBasePath, string intermediateFolder, bool treatOutputAsModule = false) | ||
| 20 | { | ||
| 21 | this.Output = output; | ||
| 22 | this.Database = database; | ||
| 23 | this.InputFilePath = inputFilePath; | ||
| 24 | this.ExportBasePath = exportBasePath; | ||
| 25 | this.IntermediateFolder = intermediateFolder; | ||
| 26 | this.TreatOutputAsModule = treatOutputAsModule; | ||
| 27 | } | ||
| 28 | |||
| 29 | public string[] ExtractedFiles { get; private set; } | ||
| 30 | |||
| 31 | private WindowsInstallerData Output { get; } | ||
| 32 | |||
| 33 | private Database Database { get; } | ||
| 34 | |||
| 35 | private string InputFilePath { get; } | ||
| 36 | |||
| 37 | private string ExportBasePath { get; } | ||
| 38 | |||
| 39 | private string IntermediateFolder { get; } | ||
| 40 | |||
| 41 | public bool TreatOutputAsModule { get; } | ||
| 42 | |||
| 43 | public void Execute() | ||
| 44 | { | ||
| 45 | var databaseBasePath = Path.GetDirectoryName(this.InputFilePath); | ||
| 46 | var cabinetFiles = new List<string>(); | ||
| 47 | var embeddedCabinets = new SortedList(); | ||
| 48 | |||
| 49 | // index all of the cabinet files | ||
| 50 | if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule) | ||
| 51 | { | ||
| 52 | embeddedCabinets.Add(0, "MergeModule.CABinet"); | ||
| 53 | } | ||
| 54 | else if (null != this.Output.Tables["Media"]) | ||
| 55 | { | ||
| 56 | foreach (MediaRow mediaRow in this.Output.Tables["Media"].Rows) | ||
| 57 | { | ||
| 58 | if (null != mediaRow.Cabinet) | ||
| 59 | { | ||
| 60 | if (OutputType.Product == this.Output.Type || | ||
| 61 | (OutputType.Transform == this.Output.Type && RowOperation.Add == mediaRow.Operation)) | ||
| 62 | { | ||
| 63 | if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
| 64 | { | ||
| 65 | embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1)); | ||
| 66 | } | ||
| 67 | else | ||
| 68 | { | ||
| 69 | cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet)); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | // extract the embedded cabinet files from the database | ||
| 77 | if (0 < embeddedCabinets.Count) | ||
| 78 | { | ||
| 79 | using (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) | ||
| 80 | { | ||
| 81 | foreach (int diskId in embeddedCabinets.Keys) | ||
| 82 | { | ||
| 83 | using (var record = new Record(1)) | ||
| 84 | { | ||
| 85 | record.SetString(1, (string)embeddedCabinets[diskId]); | ||
| 86 | streamsView.Execute(record); | ||
| 87 | } | ||
| 88 | |||
| 89 | using (var record = streamsView.Fetch()) | ||
| 90 | { | ||
| 91 | if (null != record) | ||
| 92 | { | ||
| 93 | // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive, | ||
| 94 | // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work | ||
| 95 | var cabinetFile = Path.Combine(this.IntermediateFolder, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); | ||
| 96 | |||
| 97 | // ensure the parent directory exists | ||
| 98 | Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); | ||
| 99 | |||
| 100 | using (var fs = File.Create(cabinetFile)) | ||
| 101 | { | ||
| 102 | int bytesRead; | ||
| 103 | var buffer = new byte[512]; | ||
| 104 | |||
| 105 | while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length))) | ||
| 106 | { | ||
| 107 | fs.Write(buffer, 0, bytesRead); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | cabinetFiles.Add(cabinetFile); | ||
| 112 | } | ||
| 113 | else | ||
| 114 | { | ||
| 115 | // TODO: warning about missing embedded cabinet | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | // extract the cabinet files | ||
| 123 | if (0 < cabinetFiles.Count) | ||
| 124 | { | ||
| 125 | // ensure the directory exists or extraction will fail | ||
| 126 | Directory.CreateDirectory(this.ExportBasePath); | ||
| 127 | |||
| 128 | foreach (var cabinetFile in cabinetFiles) | ||
| 129 | { | ||
| 130 | try | ||
| 131 | { | ||
| 132 | var cabinet = new Cabinet(cabinetFile); | ||
| 133 | this.ExtractedFiles = cabinet.Extract(this.ExportBasePath).ToArray(); | ||
| 134 | } | ||
| 135 | catch (FileNotFoundException) | ||
| 136 | { | ||
| 137 | throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetFile)); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } | ||
| 141 | else | ||
| 142 | { | ||
| 143 | this.ExtractedFiles = new string[0]; | ||
| 144 | } | ||
| 145 | } | ||
| 146 | } | ||
| 147 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs new file mode 100644 index 00000000..b510690e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs | |||
| @@ -0,0 +1,789 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Unbind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Text.RegularExpressions; | ||
| 11 | using WixToolset.Core.Native.Msi; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.WindowsInstaller; | ||
| 14 | using WixToolset.Extensibility.Services; | ||
| 15 | |||
| 16 | internal class UnbindDatabaseCommand | ||
| 17 | { | ||
| 18 | private List<string> exportedFiles; | ||
| 19 | |||
| 20 | public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo) | ||
| 21 | { | ||
| 22 | this.Messaging = messaging; | ||
| 23 | this.BackendHelper = backendHelper; | ||
| 24 | this.Database = database; | ||
| 25 | this.DatabasePath = databasePath; | ||
| 26 | this.OutputType = outputType; | ||
| 27 | this.ExportBasePath = exportBasePath; | ||
| 28 | this.IntermediateFolder = intermediateFolder; | ||
| 29 | this.IsAdminImage = isAdminImage; | ||
| 30 | this.SuppressDemodularization = suppressDemodularization; | ||
| 31 | this.SkipSummaryInfo = skipSummaryInfo; | ||
| 32 | |||
| 33 | this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); | ||
| 34 | } | ||
| 35 | |||
| 36 | public IMessaging Messaging { get; } | ||
| 37 | |||
| 38 | public IBackendHelper BackendHelper { get; } | ||
| 39 | |||
| 40 | public Database Database { get; } | ||
| 41 | |||
| 42 | public string DatabasePath { get; } | ||
| 43 | |||
| 44 | public OutputType OutputType { get; } | ||
| 45 | |||
| 46 | public string ExportBasePath { get; } | ||
| 47 | |||
| 48 | public string IntermediateFolder { get; } | ||
| 49 | |||
| 50 | public bool IsAdminImage { get; } | ||
| 51 | |||
| 52 | public bool SuppressDemodularization { get; } | ||
| 53 | |||
| 54 | public bool SkipSummaryInfo { get; } | ||
| 55 | |||
| 56 | public TableDefinitionCollection TableDefinitions { get; } | ||
| 57 | |||
| 58 | public IEnumerable<string> ExportedFiles => this.exportedFiles; | ||
| 59 | |||
| 60 | private int SectionCount { get; set; } | ||
| 61 | |||
| 62 | public WindowsInstallerData Execute() | ||
| 63 | { | ||
| 64 | this.exportedFiles = new List<string>(); | ||
| 65 | |||
| 66 | string modularizationGuid = null; | ||
| 67 | var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)); | ||
| 68 | View validationView = null; | ||
| 69 | |||
| 70 | // set the output type | ||
| 71 | output.Type = this.OutputType; | ||
| 72 | |||
| 73 | Directory.CreateDirectory(this.IntermediateFolder); | ||
| 74 | |||
| 75 | // get the codepage | ||
| 76 | this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); | ||
| 77 | using (var sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt"))) | ||
| 78 | { | ||
| 79 | string line; | ||
| 80 | |||
| 81 | while (null != (line = sr.ReadLine())) | ||
| 82 | { | ||
| 83 | var data = line.Split('\t'); | ||
| 84 | |||
| 85 | if (2 == data.Length) | ||
| 86 | { | ||
| 87 | output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | // get the summary information table if it exists; it won't if unbinding a transform | ||
| 93 | if (!this.SkipSummaryInfo) | ||
| 94 | { | ||
| 95 | using (var summaryInformation = new SummaryInformation(this.Database)) | ||
| 96 | { | ||
| 97 | var table = new Table(this.TableDefinitions["_SummaryInformation"]); | ||
| 98 | |||
| 99 | for (var i = 1; 19 >= i; i++) | ||
| 100 | { | ||
| 101 | var value = summaryInformation.GetProperty(i); | ||
| 102 | |||
| 103 | if (0 < value.Length) | ||
| 104 | { | ||
| 105 | var row = table.CreateRow(output.SourceLineNumbers); | ||
| 106 | row[0] = i; | ||
| 107 | row[1] = value; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | output.Tables.Add(table); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | try | ||
| 116 | { | ||
| 117 | // open a view on the validation table if it exists | ||
| 118 | if (this.Database.TableExists("_Validation")) | ||
| 119 | { | ||
| 120 | validationView = this.Database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); | ||
| 121 | } | ||
| 122 | |||
| 123 | // get the normal tables | ||
| 124 | using (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables")) | ||
| 125 | { | ||
| 126 | foreach (var tableRecord in tablesView.Records) | ||
| 127 | { | ||
| 128 | var tableName = tableRecord.GetString(1); | ||
| 129 | |||
| 130 | using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) | ||
| 131 | { | ||
| 132 | var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView); | ||
| 133 | var table = new Table(tableDefinition); | ||
| 134 | |||
| 135 | foreach (var rowRecord in tableView.Records) | ||
| 136 | { | ||
| 137 | var recordCount = rowRecord.GetFieldCount(); | ||
| 138 | var row = table.CreateRow(output.SourceLineNumbers); | ||
| 139 | |||
| 140 | for (var i = 0; recordCount > i && row.Fields.Length > i; i++) | ||
| 141 | { | ||
| 142 | if (rowRecord.IsNull(i + 1)) | ||
| 143 | { | ||
| 144 | if (!row.Fields[i].Column.Nullable) | ||
| 145 | { | ||
| 146 | // TODO: display an error for a null value in a non-nullable field OR | ||
| 147 | // display a warning and put an empty string in the value to let the compiler handle it | ||
| 148 | // (the second option is risky because the later code may make certain assumptions about | ||
| 149 | // the contents of a row value) | ||
| 150 | } | ||
| 151 | } | ||
| 152 | else | ||
| 153 | { | ||
| 154 | switch (row.Fields[i].Column.Type) | ||
| 155 | { | ||
| 156 | case ColumnType.Number: | ||
| 157 | var success = false; | ||
| 158 | var intValue = rowRecord.GetInteger(i + 1); | ||
| 159 | if (row.Fields[i].Column.IsLocalizable) | ||
| 160 | { | ||
| 161 | success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)); | ||
| 162 | } | ||
| 163 | else | ||
| 164 | { | ||
| 165 | success = row.BestEffortSetField(i, intValue); | ||
| 166 | } | ||
| 167 | |||
| 168 | if (!success) | ||
| 169 | { | ||
| 170 | this.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); | ||
| 171 | } | ||
| 172 | break; | ||
| 173 | case ColumnType.Object: | ||
| 174 | var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; | ||
| 175 | |||
| 176 | if (null != this.ExportBasePath) | ||
| 177 | { | ||
| 178 | var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); | ||
| 179 | sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile); | ||
| 180 | |||
| 181 | // ensure the parent directory exists | ||
| 182 | System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName)); | ||
| 183 | |||
| 184 | using (var fs = System.IO.File.Create(sourceFile)) | ||
| 185 | { | ||
| 186 | int bytesRead; | ||
| 187 | var buffer = new byte[512]; | ||
| 188 | |||
| 189 | while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) | ||
| 190 | { | ||
| 191 | fs.Write(buffer, 0, bytesRead); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | this.exportedFiles.Add(sourceFile); | ||
| 196 | } | ||
| 197 | |||
| 198 | row[i] = sourceFile; | ||
| 199 | break; | ||
| 200 | default: | ||
| 201 | var value = rowRecord.GetString(i + 1); | ||
| 202 | |||
| 203 | switch (row.Fields[i].Column.Category) | ||
| 204 | { | ||
| 205 | case ColumnCategory.Guid: | ||
| 206 | value = value.ToUpper(CultureInfo.InvariantCulture); | ||
| 207 | break; | ||
| 208 | } | ||
| 209 | |||
| 210 | // de-modularize | ||
| 211 | if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) | ||
| 212 | { | ||
| 213 | var modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); | ||
| 214 | |||
| 215 | if (null == modularizationGuid) | ||
| 216 | { | ||
| 217 | var match = modularization.Match(value); | ||
| 218 | if (match.Success) | ||
| 219 | { | ||
| 220 | modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | value = modularization.Replace(value, String.Empty); | ||
| 225 | } | ||
| 226 | |||
| 227 | // escape "$(" for the preprocessor | ||
| 228 | value = value.Replace("$(", "$$("); | ||
| 229 | |||
| 230 | // escape things that look like wix variables | ||
| 231 | // TODO: Evaluate this requirement. | ||
| 232 | //var matches = Common.WixVariableRegex.Matches(value); | ||
| 233 | //for (var j = matches.Count - 1; 0 <= j; j--) | ||
| 234 | //{ | ||
| 235 | // value = value.Insert(matches[j].Index, "!"); | ||
| 236 | //} | ||
| 237 | |||
| 238 | row[i] = value; | ||
| 239 | break; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | output.Tables.Add(table); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | } | ||
| 249 | } | ||
| 250 | finally | ||
| 251 | { | ||
| 252 | if (null != validationView) | ||
| 253 | { | ||
| 254 | validationView.Close(); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | // set the modularization guid as the PackageCode | ||
| 259 | if (null != modularizationGuid) | ||
| 260 | { | ||
| 261 | var table = output.Tables["_SummaryInformation"]; | ||
| 262 | |||
| 263 | foreach (var row in table.Rows) | ||
| 264 | { | ||
| 265 | if (9 == (int)row[0]) // PID_REVNUMBER | ||
| 266 | { | ||
| 267 | row[1] = modularizationGuid; | ||
| 268 | } | ||
| 269 | } | ||
| 270 | } | ||
| 271 | |||
| 272 | if (this.IsAdminImage) | ||
| 273 | { | ||
| 274 | this.GenerateWixFileTable(this.DatabasePath, output); | ||
| 275 | this.GenerateSectionIds(output); | ||
| 276 | } | ||
| 277 | |||
| 278 | return output; | ||
| 279 | } | ||
| 280 | |||
| 281 | private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView) | ||
| 282 | { | ||
| 283 | // Use our table definitions whenever possible since they will be used when compiling the source code anyway. | ||
| 284 | // This also allows us to take advantage of WiX concepts like localizable columns which current code assumes. | ||
| 285 | if (this.TableDefinitions.Contains(tableName)) | ||
| 286 | { | ||
| 287 | return this.TableDefinitions[tableName]; | ||
| 288 | } | ||
| 289 | |||
| 290 | ColumnDefinition[] columns; | ||
| 291 | using (Record columnNameRecord = tableView.GetColumnNames(), | ||
| 292 | columnTypeRecord = tableView.GetColumnTypes()) | ||
| 293 | { | ||
| 294 | // index the primary keys | ||
| 295 | var tablePrimaryKeys = new HashSet<string>(); | ||
| 296 | using (var primaryKeysRecord = this.Database.PrimaryKeys(tableName)) | ||
| 297 | { | ||
| 298 | var primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); | ||
| 299 | |||
| 300 | for (var i = 1; i <= primaryKeysFieldCount; i++) | ||
| 301 | { | ||
| 302 | tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | var columnCount = columnNameRecord.GetFieldCount(); | ||
| 307 | columns = new ColumnDefinition[columnCount]; | ||
| 308 | for (var i = 1; i <= columnCount; i++) | ||
| 309 | { | ||
| 310 | var columnName = columnNameRecord.GetString(i); | ||
| 311 | var idtType = columnTypeRecord.GetString(i); | ||
| 312 | |||
| 313 | ColumnType columnType; | ||
| 314 | int length; | ||
| 315 | bool nullable; | ||
| 316 | |||
| 317 | var columnCategory = ColumnCategory.Unknown; | ||
| 318 | var columnModularizeType = ColumnModularizeType.None; | ||
| 319 | var primary = tablePrimaryKeys.Contains(columnName); | ||
| 320 | int? minValue = null; | ||
| 321 | int? maxValue = null; | ||
| 322 | string keyTable = null; | ||
| 323 | int? keyColumn = null; | ||
| 324 | string category = null; | ||
| 325 | string set = null; | ||
| 326 | string description = null; | ||
| 327 | |||
| 328 | // get the column type, length, and whether its nullable | ||
| 329 | switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) | ||
| 330 | { | ||
| 331 | case 'i': | ||
| 332 | columnType = ColumnType.Number; | ||
| 333 | break; | ||
| 334 | case 'l': | ||
| 335 | columnType = ColumnType.Localized; | ||
| 336 | break; | ||
| 337 | case 's': | ||
| 338 | columnType = ColumnType.String; | ||
| 339 | break; | ||
| 340 | case 'v': | ||
| 341 | columnType = ColumnType.Object; | ||
| 342 | break; | ||
| 343 | default: | ||
| 344 | // TODO: error | ||
| 345 | columnType = ColumnType.Unknown; | ||
| 346 | break; | ||
| 347 | } | ||
| 348 | length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); | ||
| 349 | nullable = Char.IsUpper(idtType[0]); | ||
| 350 | |||
| 351 | // try to get validation information | ||
| 352 | if (null != validationView) | ||
| 353 | { | ||
| 354 | using (var validationRecord = new Record(2)) | ||
| 355 | { | ||
| 356 | validationRecord.SetString(1, tableName); | ||
| 357 | validationRecord.SetString(2, columnName); | ||
| 358 | |||
| 359 | validationView.Execute(validationRecord); | ||
| 360 | } | ||
| 361 | |||
| 362 | using (var validationRecord = validationView.Fetch()) | ||
| 363 | { | ||
| 364 | if (null != validationRecord) | ||
| 365 | { | ||
| 366 | var validationNullable = validationRecord.GetString(3); | ||
| 367 | minValue = validationRecord.IsNull(4) ? null : (int?)validationRecord.GetInteger(4); | ||
| 368 | maxValue = validationRecord.IsNull(5) ? null : (int?)validationRecord.GetInteger(5); | ||
| 369 | keyTable = validationRecord.IsNull(6) ? null : validationRecord.GetString(6); | ||
| 370 | keyColumn = validationRecord.IsNull(7) ? null : (int?)validationRecord.GetInteger(7); | ||
| 371 | category = validationRecord.IsNull(8) ? null : validationRecord.GetString(8); | ||
| 372 | set = validationRecord.IsNull(9) ? null : validationRecord.GetString(9); | ||
| 373 | description = validationRecord.IsNull(10) ? null : validationRecord.GetString(10); | ||
| 374 | |||
| 375 | // check the validation nullable value against the column definition | ||
| 376 | if (null == validationNullable) | ||
| 377 | { | ||
| 378 | // TODO: warn for illegal validation nullable column | ||
| 379 | } | ||
| 380 | else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) | ||
| 381 | { | ||
| 382 | // TODO: warn for mismatch between column definition and validation nullable | ||
| 383 | } | ||
| 384 | |||
| 385 | // convert category to ColumnCategory | ||
| 386 | if (null != category) | ||
| 387 | { | ||
| 388 | try | ||
| 389 | { | ||
| 390 | columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); | ||
| 391 | } | ||
| 392 | catch (ArgumentException) | ||
| 393 | { | ||
| 394 | columnCategory = ColumnCategory.Unknown; | ||
| 395 | } | ||
| 396 | } | ||
| 397 | } | ||
| 398 | else | ||
| 399 | { | ||
| 400 | // TODO: warn about no validation information | ||
| 401 | } | ||
| 402 | } | ||
| 403 | } | ||
| 404 | |||
| 405 | // guess the modularization type | ||
| 406 | if ("Icon" == keyTable && 1 == keyColumn) | ||
| 407 | { | ||
| 408 | columnModularizeType = ColumnModularizeType.Icon; | ||
| 409 | } | ||
| 410 | else if ("Condition" == columnName) | ||
| 411 | { | ||
| 412 | columnModularizeType = ColumnModularizeType.Condition; | ||
| 413 | } | ||
| 414 | else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory) | ||
| 415 | { | ||
| 416 | columnModularizeType = ColumnModularizeType.Property; | ||
| 417 | } | ||
| 418 | else if (ColumnCategory.Identifier == columnCategory) | ||
| 419 | { | ||
| 420 | columnModularizeType = ColumnModularizeType.Column; | ||
| 421 | } | ||
| 422 | |||
| 423 | columns[i - 1] = new ColumnDefinition(columnName, columnType, length, primary, nullable, columnCategory, minValue, maxValue, keyTable, keyColumn, set, description, columnModularizeType, (ColumnType.Localized == columnType), true); | ||
| 424 | } | ||
| 425 | } | ||
| 426 | |||
| 427 | return new TableDefinition(tableName, null, columns, false); | ||
| 428 | } | ||
| 429 | |||
| 430 | /// <summary> | ||
| 431 | /// Generates the WixFile table based on a path to an admin image msi and an Output. | ||
| 432 | /// </summary> | ||
| 433 | /// <param name="databaseFile">The path to the msi database file in an admin image.</param> | ||
| 434 | /// <param name="output">The Output that represents the msi database.</param> | ||
| 435 | private void GenerateWixFileTable(string databaseFile, WindowsInstallerData output) | ||
| 436 | { | ||
| 437 | throw new NotImplementedException(); | ||
| 438 | #if TODO_FIX_UNBINDING_FILES | ||
| 439 | var adminRootPath = Path.GetDirectoryName(databaseFile); | ||
| 440 | |||
| 441 | var componentDirectoryIndex = new Hashtable(); | ||
| 442 | var componentTable = output.Tables["Component"]; | ||
| 443 | foreach (var row in componentTable.Rows) | ||
| 444 | { | ||
| 445 | componentDirectoryIndex.Add(row[0], row[2]); | ||
| 446 | } | ||
| 447 | |||
| 448 | // Index full source paths for all directories | ||
| 449 | var directoryDirectoryParentIndex = new Hashtable(); | ||
| 450 | var directoryFullPathIndex = new Hashtable(); | ||
| 451 | var directorySourceNameIndex = new Hashtable(); | ||
| 452 | var directoryTable = output.Tables["Directory"]; | ||
| 453 | foreach (var row in directoryTable.Rows) | ||
| 454 | { | ||
| 455 | directoryDirectoryParentIndex.Add(row[0], row[1]); | ||
| 456 | if (null == row[1]) | ||
| 457 | { | ||
| 458 | directoryFullPathIndex.Add(row[0], adminRootPath); | ||
| 459 | } | ||
| 460 | else | ||
| 461 | { | ||
| 462 | directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2])); | ||
| 463 | } | ||
| 464 | } | ||
| 465 | |||
| 466 | foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) | ||
| 467 | { | ||
| 468 | if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) | ||
| 469 | { | ||
| 470 | this.GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | var fileTable = output.Tables["File"]; | ||
| 475 | var wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]); | ||
| 476 | foreach (var row in fileTable.Rows) | ||
| 477 | { | ||
| 478 | var wixFileRow = new WixFileRow(null, this.TableDefinitions["WixFile"]); | ||
| 479 | wixFileRow.File = (string)row[0]; | ||
| 480 | wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]]; | ||
| 481 | wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2])); | ||
| 482 | |||
| 483 | if (!File.Exists(wixFileRow.Source)) | ||
| 484 | { | ||
| 485 | throw new WixException(ErrorMessages.WixFileNotFound(wixFileRow.Source)); | ||
| 486 | } | ||
| 487 | |||
| 488 | wixFileTable.Rows.Add(wixFileRow); | ||
| 489 | } | ||
| 490 | #endif | ||
| 491 | } | ||
| 492 | |||
| 493 | /// <summary> | ||
| 494 | /// Gets the full path of a directory. Populates the full path index with the directory's full path and all of its parent directorie's full paths. | ||
| 495 | /// </summary> | ||
| 496 | /// <param name="directory">The directory identifier.</param> | ||
| 497 | /// <param name="directoryDirectoryParentIndex">The Hashtable containing all the directory to directory parent mapping.</param> | ||
| 498 | /// <param name="directorySourceNameIndex">The Hashtable containing all the directory to source name mapping.</param> | ||
| 499 | /// <param name="directoryFullPathIndex">The Hashtable containing a mapping between all of the directories and their previously calculated full paths.</param> | ||
| 500 | /// <returns>The full path to the directory.</returns> | ||
| 501 | private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex) | ||
| 502 | { | ||
| 503 | var parent = (string)directoryDirectoryParentIndex[directory]; | ||
| 504 | var sourceName = (string)directorySourceNameIndex[directory]; | ||
| 505 | |||
| 506 | string parentFullPath; | ||
| 507 | if (directoryFullPathIndex.ContainsKey(parent)) | ||
| 508 | { | ||
| 509 | parentFullPath = (string)directoryFullPathIndex[parent]; | ||
| 510 | } | ||
| 511 | else | ||
| 512 | { | ||
| 513 | parentFullPath = this.GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); | ||
| 514 | } | ||
| 515 | |||
| 516 | if (null == sourceName) | ||
| 517 | { | ||
| 518 | sourceName = String.Empty; | ||
| 519 | } | ||
| 520 | |||
| 521 | var fullPath = Path.Combine(parentFullPath, sourceName); | ||
| 522 | directoryFullPathIndex.Add(directory, fullPath); | ||
| 523 | |||
| 524 | return fullPath; | ||
| 525 | } | ||
| 526 | |||
| 527 | /// <summary> | ||
| 528 | /// Get the source name in an admin image. | ||
| 529 | /// </summary> | ||
| 530 | /// <param name="value">The Filename value.</param> | ||
| 531 | /// <returns>The source name of the directory in an admin image.</returns> | ||
| 532 | private string GetAdminSourceName(string value) | ||
| 533 | { | ||
| 534 | string name = null; | ||
| 535 | string[] names; | ||
| 536 | string shortname = null; | ||
| 537 | string shortsourcename = null; | ||
| 538 | string sourcename = null; | ||
| 539 | |||
| 540 | names = this.BackendHelper.SplitMsiFileName(value); | ||
| 541 | |||
| 542 | if (null != names[0] && "." != names[0]) | ||
| 543 | { | ||
| 544 | if (null != names[1]) | ||
| 545 | { | ||
| 546 | shortname = names[0]; | ||
| 547 | } | ||
| 548 | else | ||
| 549 | { | ||
| 550 | name = names[0]; | ||
| 551 | } | ||
| 552 | } | ||
| 553 | |||
| 554 | if (null != names[1]) | ||
| 555 | { | ||
| 556 | name = names[1]; | ||
| 557 | } | ||
| 558 | |||
| 559 | if (null != names[2]) | ||
| 560 | { | ||
| 561 | if (null != names[3]) | ||
| 562 | { | ||
| 563 | shortsourcename = names[2]; | ||
| 564 | } | ||
| 565 | else | ||
| 566 | { | ||
| 567 | sourcename = names[2]; | ||
| 568 | } | ||
| 569 | } | ||
| 570 | |||
| 571 | if (null != names[3]) | ||
| 572 | { | ||
| 573 | sourcename = names[3]; | ||
| 574 | } | ||
| 575 | |||
| 576 | if (null != sourcename) | ||
| 577 | { | ||
| 578 | return sourcename; | ||
| 579 | } | ||
| 580 | else if (null != shortsourcename) | ||
| 581 | { | ||
| 582 | return shortsourcename; | ||
| 583 | } | ||
| 584 | else if (null != name) | ||
| 585 | { | ||
| 586 | return name; | ||
| 587 | } | ||
| 588 | else | ||
| 589 | { | ||
| 590 | return shortname; | ||
| 591 | } | ||
| 592 | } | ||
| 593 | |||
| 594 | /// <summary> | ||
| 595 | /// Creates section ids on rows which form logical groupings of resources. | ||
| 596 | /// </summary> | ||
| 597 | /// <param name="output">The Output that represents the msi database.</param> | ||
| 598 | private void GenerateSectionIds(WindowsInstallerData output) | ||
| 599 | { | ||
| 600 | // First assign and index section ids for the tables that are in their own sections. | ||
| 601 | this.AssignSectionIdsToTable(output.Tables["Binary"], 0); | ||
| 602 | var componentSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Component"], 0); | ||
| 603 | var customActionSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["CustomAction"], 0); | ||
| 604 | this.AssignSectionIdsToTable(output.Tables["Directory"], 0); | ||
| 605 | var featureSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Feature"], 0); | ||
| 606 | this.AssignSectionIdsToTable(output.Tables["Icon"], 0); | ||
| 607 | var digitalCertificateSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0); | ||
| 608 | this.AssignSectionIdsToTable(output.Tables["Property"], 0); | ||
| 609 | |||
| 610 | // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here. | ||
| 611 | var fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0); | ||
| 612 | var appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5); | ||
| 613 | var odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0); | ||
| 614 | var odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0); | ||
| 615 | var registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0); | ||
| 616 | var serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0); | ||
| 617 | |||
| 618 | // Now handle all the tables which only rely on previous indexes and order does not matter. | ||
| 619 | foreach (var table in output.Tables) | ||
| 620 | { | ||
| 621 | switch (table.Name) | ||
| 622 | { | ||
| 623 | case "WixFile": | ||
| 624 | case "MsiFileHash": | ||
| 625 | ConnectTableToSection(table, fileSectionIdIndex, 0); | ||
| 626 | break; | ||
| 627 | case "MsiAssembly": | ||
| 628 | case "MsiAssemblyName": | ||
| 629 | ConnectTableToSection(table, componentSectionIdIndex, 0); | ||
| 630 | break; | ||
| 631 | case "MsiPackageCertificate": | ||
| 632 | case "MsiPatchCertificate": | ||
| 633 | ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1); | ||
| 634 | break; | ||
| 635 | case "CreateFolder": | ||
| 636 | case "FeatureComponents": | ||
| 637 | case "MoveFile": | ||
| 638 | case "ReserveCost": | ||
| 639 | case "ODBCTranslator": | ||
| 640 | ConnectTableToSection(table, componentSectionIdIndex, 1); | ||
| 641 | break; | ||
| 642 | case "TypeLib": | ||
| 643 | ConnectTableToSection(table, componentSectionIdIndex, 2); | ||
| 644 | break; | ||
| 645 | case "Shortcut": | ||
| 646 | case "Environment": | ||
| 647 | ConnectTableToSection(table, componentSectionIdIndex, 3); | ||
| 648 | break; | ||
| 649 | case "RemoveRegistry": | ||
| 650 | ConnectTableToSection(table, componentSectionIdIndex, 4); | ||
| 651 | break; | ||
| 652 | case "ServiceControl": | ||
| 653 | ConnectTableToSection(table, componentSectionIdIndex, 5); | ||
| 654 | break; | ||
| 655 | case "IniFile": | ||
| 656 | case "RemoveIniFile": | ||
| 657 | ConnectTableToSection(table, componentSectionIdIndex, 7); | ||
| 658 | break; | ||
| 659 | case "AppId": | ||
| 660 | ConnectTableToSection(table, appIdSectionIdIndex, 0); | ||
| 661 | break; | ||
| 662 | case "Condition": | ||
| 663 | ConnectTableToSection(table, featureSectionIdIndex, 0); | ||
| 664 | break; | ||
| 665 | case "ODBCSourceAttribute": | ||
| 666 | ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0); | ||
| 667 | break; | ||
| 668 | case "ODBCAttribute": | ||
| 669 | ConnectTableToSection(table, odbcDriverSectionIdIndex, 0); | ||
| 670 | break; | ||
| 671 | case "AdminExecuteSequence": | ||
| 672 | case "AdminUISequence": | ||
| 673 | case "AdvtExecuteSequence": | ||
| 674 | case "AdvtUISequence": | ||
| 675 | case "InstallExecuteSequence": | ||
| 676 | case "InstallUISequence": | ||
| 677 | ConnectTableToSection(table, customActionSectionIdIndex, 0); | ||
| 678 | break; | ||
| 679 | case "LockPermissions": | ||
| 680 | case "MsiLockPermissions": | ||
| 681 | foreach (var row in table.Rows) | ||
| 682 | { | ||
| 683 | var lockObject = (string)row[0]; | ||
| 684 | var tableName = (string)row[1]; | ||
| 685 | switch (tableName) | ||
| 686 | { | ||
| 687 | case "File": | ||
| 688 | row.SectionId = (string)fileSectionIdIndex[lockObject]; | ||
| 689 | break; | ||
| 690 | case "Registry": | ||
| 691 | row.SectionId = (string)registrySectionIdIndex[lockObject]; | ||
| 692 | break; | ||
| 693 | case "ServiceInstall": | ||
| 694 | row.SectionId = (string)serviceInstallSectionIdIndex[lockObject]; | ||
| 695 | break; | ||
| 696 | } | ||
| 697 | } | ||
| 698 | break; | ||
| 699 | } | ||
| 700 | } | ||
| 701 | |||
| 702 | // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids. | ||
| 703 | //foreach (IUnbinderExtension extension in this.unbinderExtensions) | ||
| 704 | //{ | ||
| 705 | // extension.GenerateSectionIds(output); | ||
| 706 | //} | ||
| 707 | } | ||
| 708 | |||
| 709 | /// <summary> | ||
| 710 | /// Creates new section ids on all the rows in a table. | ||
| 711 | /// </summary> | ||
| 712 | /// <param name="table">The table to add sections to.</param> | ||
| 713 | /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param> | ||
| 714 | /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns> | ||
| 715 | private Hashtable AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex) | ||
| 716 | { | ||
| 717 | var hashtable = new Hashtable(); | ||
| 718 | if (null != table) | ||
| 719 | { | ||
| 720 | foreach (var row in table.Rows) | ||
| 721 | { | ||
| 722 | row.SectionId = this.GetNewSectionId(); | ||
| 723 | hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId); | ||
| 724 | } | ||
| 725 | } | ||
| 726 | return hashtable; | ||
| 727 | } | ||
| 728 | |||
| 729 | /// <summary> | ||
| 730 | /// Connects a table's rows to an already sectioned table. | ||
| 731 | /// </summary> | ||
| 732 | /// <param name="table">The table containing rows that need to be connected to sections.</param> | ||
| 733 | /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param> | ||
| 734 | /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param> | ||
| 735 | private static void ConnectTableToSection(Table table, Hashtable sectionIdIndex, int rowIndex) | ||
| 736 | { | ||
| 737 | if (null != table) | ||
| 738 | { | ||
| 739 | foreach (var row in table.Rows) | ||
| 740 | { | ||
| 741 | if (sectionIdIndex.ContainsKey(row[rowIndex])) | ||
| 742 | { | ||
| 743 | row.SectionId = (string)sectionIdIndex[row[rowIndex]]; | ||
| 744 | } | ||
| 745 | } | ||
| 746 | } | ||
| 747 | } | ||
| 748 | |||
| 749 | /// <summary> | ||
| 750 | /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it. | ||
| 751 | /// </summary> | ||
| 752 | /// <param name="table">The table containing rows that need to be connected to sections.</param> | ||
| 753 | /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param> | ||
| 754 | /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param> | ||
| 755 | /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param> | ||
| 756 | /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns> | ||
| 757 | private static Hashtable ConnectTableToSectionAndIndex(Table table, Hashtable sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex) | ||
| 758 | { | ||
| 759 | var newHashTable = new Hashtable(); | ||
| 760 | if (null != table) | ||
| 761 | { | ||
| 762 | foreach (var row in table.Rows) | ||
| 763 | { | ||
| 764 | if (!sectionIdIndex.ContainsKey(row[rowIndex])) | ||
| 765 | { | ||
| 766 | continue; | ||
| 767 | } | ||
| 768 | |||
| 769 | row.SectionId = (string)sectionIdIndex[row[rowIndex]]; | ||
| 770 | if (null != row[rowPrimaryKeyIndex]) | ||
| 771 | { | ||
| 772 | newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId); | ||
| 773 | } | ||
| 774 | } | ||
| 775 | } | ||
| 776 | return newHashTable; | ||
| 777 | } | ||
| 778 | |||
| 779 | /// <summary> | ||
| 780 | /// Creates a new section identifier to be used when adding a section to an output. | ||
| 781 | /// </summary> | ||
| 782 | /// <returns>A string representing a new section id.</returns> | ||
| 783 | private string GetNewSectionId() | ||
| 784 | { | ||
| 785 | this.SectionCount++; | ||
| 786 | return "wix.section." + this.SectionCount.ToString(CultureInfo.InvariantCulture); | ||
| 787 | } | ||
| 788 | } | ||
| 789 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs new file mode 100644 index 00000000..75ee6307 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Unbind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.ComponentModel; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Extensibility.Data; | ||
| 9 | using WixToolset.Core.Native.Msi; | ||
| 10 | |||
| 11 | internal class UnbindMsiOrMsmCommand | ||
| 12 | { | ||
| 13 | public UnbindMsiOrMsmCommand(IUnbindContext context) | ||
| 14 | { | ||
| 15 | this.Context = context; | ||
| 16 | } | ||
| 17 | |||
| 18 | public IUnbindContext Context { get; } | ||
| 19 | |||
| 20 | public Intermediate Execute() | ||
| 21 | { | ||
| 22 | #if TODO_PATCHING | ||
| 23 | Output output; | ||
| 24 | |||
| 25 | try | ||
| 26 | { | ||
| 27 | using (Database database = new Database(this.Context.InputFilePath, OpenDatabase.ReadOnly)) | ||
| 28 | { | ||
| 29 | var unbindCommand = new UnbindDatabaseCommand(this.Context.Messaging, database, this.Context.InputFilePath, OutputType.Product, this.Context.ExportBasePath, this.Context.IntermediateFolder, this.Context.IsAdminImage, this.Context.SuppressDemodularization, skipSummaryInfo: false); | ||
| 30 | output = unbindCommand.Execute(); | ||
| 31 | |||
| 32 | // extract the files from the cabinets | ||
| 33 | if (!String.IsNullOrEmpty(this.Context.ExportBasePath) && !this.Context.SuppressExtractCabinets) | ||
| 34 | { | ||
| 35 | var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.InputFilePath, this.Context.ExportBasePath, this.Context.IntermediateFolder); | ||
| 36 | extractCommand.Execute(); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | catch (Win32Exception e) | ||
| 41 | { | ||
| 42 | if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED | ||
| 43 | { | ||
| 44 | throw new WixException(WixErrors.OpenDatabaseFailed(this.Context.InputFilePath)); | ||
| 45 | } | ||
| 46 | |||
| 47 | throw; | ||
| 48 | } | ||
| 49 | |||
| 50 | return output; | ||
| 51 | #endif | ||
| 52 | throw new NotImplementedException(); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs new file mode 100644 index 00000000..f40aed4e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs | |||
| @@ -0,0 +1,309 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller.Unbind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.ComponentModel; | ||
| 9 | using System.Globalization; | ||
| 10 | using System.IO; | ||
| 11 | using System.Linq; | ||
| 12 | using WixToolset.Core.Native.Msi; | ||
| 13 | using WixToolset.Core.WindowsInstaller.Bind; | ||
| 14 | using WixToolset.Data; | ||
| 15 | using WixToolset.Data.WindowsInstaller; | ||
| 16 | using WixToolset.Extensibility.Services; | ||
| 17 | |||
| 18 | internal class UnbindTransformCommand | ||
| 19 | { | ||
| 20 | public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, string transformFile, string exportBasePath, string intermediateFolder) | ||
| 21 | { | ||
| 22 | this.Messaging = messaging; | ||
| 23 | this.BackendHelper = backendHelper; | ||
| 24 | this.TransformFile = transformFile; | ||
| 25 | this.ExportBasePath = exportBasePath; | ||
| 26 | this.IntermediateFolder = intermediateFolder; | ||
| 27 | |||
| 28 | this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); | ||
| 29 | } | ||
| 30 | |||
| 31 | private IMessaging Messaging { get; } | ||
| 32 | |||
| 33 | private IBackendHelper BackendHelper { get; } | ||
| 34 | |||
| 35 | private string TransformFile { get; } | ||
| 36 | |||
| 37 | private string ExportBasePath { get; } | ||
| 38 | |||
| 39 | private string IntermediateFolder { get; } | ||
| 40 | |||
| 41 | private TableDefinitionCollection TableDefinitions { get; } | ||
| 42 | |||
| 43 | private string EmptyFile { get; set; } | ||
| 44 | |||
| 45 | public WindowsInstallerData Execute() | ||
| 46 | { | ||
| 47 | var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile)); | ||
| 48 | transform.Type = OutputType.Transform; | ||
| 49 | |||
| 50 | // get the summary information table | ||
| 51 | using (var summaryInformation = new SummaryInformation(this.TransformFile)) | ||
| 52 | { | ||
| 53 | var table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); | ||
| 54 | |||
| 55 | for (var i = 1; 19 >= i; i++) | ||
| 56 | { | ||
| 57 | var value = summaryInformation.GetProperty(i); | ||
| 58 | |||
| 59 | if (0 < value.Length) | ||
| 60 | { | ||
| 61 | var row = table.CreateRow(transform.SourceLineNumbers); | ||
| 62 | row[0] = i; | ||
| 63 | row[1] = value; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | // create a schema msi which hopefully matches the table schemas in the transform | ||
| 69 | var schemaOutput = new WindowsInstallerData(null); | ||
| 70 | var msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi"); | ||
| 71 | foreach (var tableDefinition in this.TableDefinitions) | ||
| 72 | { | ||
| 73 | // skip unreal tables and the Patch table | ||
| 74 | if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name) | ||
| 75 | { | ||
| 76 | schemaOutput.EnsureTable(tableDefinition); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | var addedRows = new Dictionary<string, Row>(); | ||
| 81 | Table transformViewTable; | ||
| 82 | |||
| 83 | // Bind the schema msi. | ||
| 84 | this.GenerateDatabase(schemaOutput, msiDatabaseFile); | ||
| 85 | |||
| 86 | // apply the transform to the database and retrieve the modifications | ||
| 87 | using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) | ||
| 88 | { | ||
| 89 | // apply the transform with the ViewTransform option to collect all the modifications | ||
| 90 | msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); | ||
| 91 | |||
| 92 | // unbind the database | ||
| 93 | var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); | ||
| 94 | var transformViewOutput = unbindCommand.Execute(); | ||
| 95 | |||
| 96 | // index the added and possibly modified rows (added rows may also appears as modified rows) | ||
| 97 | transformViewTable = transformViewOutput.Tables["_TransformView"]; | ||
| 98 | var modifiedRows = new Hashtable(); | ||
| 99 | foreach (var row in transformViewTable.Rows) | ||
| 100 | { | ||
| 101 | var tableName = (string)row[0]; | ||
| 102 | var columnName = (string)row[1]; | ||
| 103 | var primaryKeys = (string)row[2]; | ||
| 104 | |||
| 105 | if ("INSERT" == columnName) | ||
| 106 | { | ||
| 107 | var index = String.Concat(tableName, ':', primaryKeys); | ||
| 108 | |||
| 109 | addedRows.Add(index, null); | ||
| 110 | } | ||
| 111 | else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row | ||
| 112 | { | ||
| 113 | var index = String.Concat(tableName, ':', primaryKeys); | ||
| 114 | |||
| 115 | modifiedRows[index] = row; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | // create placeholder rows for modified rows to make the transform insert the updated values when its applied | ||
| 120 | foreach (Row row in modifiedRows.Values) | ||
| 121 | { | ||
| 122 | var tableName = (string)row[0]; | ||
| 123 | var columnName = (string)row[1]; | ||
| 124 | var primaryKeys = (string)row[2]; | ||
| 125 | |||
| 126 | var index = String.Concat(tableName, ':', primaryKeys); | ||
| 127 | |||
| 128 | // ignore information for added rows | ||
| 129 | if (!addedRows.ContainsKey(index)) | ||
| 130 | { | ||
| 131 | var table = schemaOutput.Tables[tableName]; | ||
| 132 | this.CreateRow(table, primaryKeys, true); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | // Re-bind the schema output with the placeholder rows. | ||
| 138 | this.GenerateDatabase(schemaOutput, msiDatabaseFile); | ||
| 139 | |||
| 140 | // apply the transform to the database and retrieve the modifications | ||
| 141 | using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) | ||
| 142 | { | ||
| 143 | try | ||
| 144 | { | ||
| 145 | // apply the transform | ||
| 146 | msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All); | ||
| 147 | |||
| 148 | // commit the database to guard against weird errors with streams | ||
| 149 | msiDatabase.Commit(); | ||
| 150 | } | ||
| 151 | catch (Win32Exception ex) | ||
| 152 | { | ||
| 153 | if (0x65B == ex.NativeErrorCode) | ||
| 154 | { | ||
| 155 | // this commonly happens when the transform was built | ||
| 156 | // against a database schema different from the internal | ||
| 157 | // table definitions | ||
| 158 | throw new WixException(ErrorMessages.TransformSchemaMismatch()); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | // unbind the database | ||
| 163 | var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); | ||
| 164 | var output = unbindCommand.Execute(); | ||
| 165 | |||
| 166 | // index all the rows to easily find modified rows | ||
| 167 | var rows = new Dictionary<string, Row>(); | ||
| 168 | foreach (var table in output.Tables) | ||
| 169 | { | ||
| 170 | foreach (var row in table.Rows) | ||
| 171 | { | ||
| 172 | rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | // process the _TransformView rows into transform rows | ||
| 177 | foreach (var row in transformViewTable.Rows) | ||
| 178 | { | ||
| 179 | var tableName = (string)row[0]; | ||
| 180 | var columnName = (string)row[1]; | ||
| 181 | var primaryKeys = (string)row[2]; | ||
| 182 | |||
| 183 | var table = transform.EnsureTable(this.TableDefinitions[tableName]); | ||
| 184 | |||
| 185 | if ("CREATE" == columnName) // added table | ||
| 186 | { | ||
| 187 | table.Operation = TableOperation.Add; | ||
| 188 | } | ||
| 189 | else if ("DELETE" == columnName) // deleted row | ||
| 190 | { | ||
| 191 | var deletedRow = this.CreateRow(table, primaryKeys, false); | ||
| 192 | deletedRow.Operation = RowOperation.Delete; | ||
| 193 | } | ||
| 194 | else if ("DROP" == columnName) // dropped table | ||
| 195 | { | ||
| 196 | table.Operation = TableOperation.Drop; | ||
| 197 | } | ||
| 198 | else if ("INSERT" == columnName) // added row | ||
| 199 | { | ||
| 200 | var index = String.Concat(tableName, ':', primaryKeys); | ||
| 201 | var addedRow = rows[index]; | ||
| 202 | addedRow.Operation = RowOperation.Add; | ||
| 203 | table.Rows.Add(addedRow); | ||
| 204 | } | ||
| 205 | else if (null != primaryKeys) // modified row | ||
| 206 | { | ||
| 207 | var index = String.Concat(tableName, ':', primaryKeys); | ||
| 208 | |||
| 209 | // the _TransformView table includes information for added rows | ||
| 210 | // that looks like modified rows so it sometimes needs to be ignored | ||
| 211 | if (!addedRows.ContainsKey(index)) | ||
| 212 | { | ||
| 213 | var modifiedRow = rows[index]; | ||
| 214 | |||
| 215 | // mark the field as modified | ||
| 216 | var indexOfModifiedValue = -1; | ||
| 217 | for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i) | ||
| 218 | { | ||
| 219 | if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal)) | ||
| 220 | { | ||
| 221 | indexOfModifiedValue = i; | ||
| 222 | break; | ||
| 223 | } | ||
| 224 | } | ||
| 225 | modifiedRow.Fields[indexOfModifiedValue].Modified = true; | ||
| 226 | |||
| 227 | // move the modified row into the transform the first time its encountered | ||
| 228 | if (RowOperation.None == modifiedRow.Operation) | ||
| 229 | { | ||
| 230 | modifiedRow.Operation = RowOperation.Modify; | ||
| 231 | table.Rows.Add(modifiedRow); | ||
| 232 | } | ||
| 233 | } | ||
| 234 | } | ||
| 235 | else // added column | ||
| 236 | { | ||
| 237 | var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); | ||
| 238 | column.Added = true; | ||
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | return transform; | ||
| 244 | } | ||
| 245 | |||
| 246 | /// <summary> | ||
| 247 | /// Create a deleted or modified row. | ||
| 248 | /// </summary> | ||
| 249 | /// <param name="table">The table containing the row.</param> | ||
| 250 | /// <param name="primaryKeys">The primary keys of the row.</param> | ||
| 251 | /// <param name="setRequiredFields">Option to set all required fields with placeholder values.</param> | ||
| 252 | /// <returns>The new row.</returns> | ||
| 253 | private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields) | ||
| 254 | { | ||
| 255 | var row = table.CreateRow(null); | ||
| 256 | |||
| 257 | var primaryKeyParts = primaryKeys.Split('\t'); | ||
| 258 | var primaryKeyPartIndex = 0; | ||
| 259 | |||
| 260 | for (var i = 0; i < table.Definition.Columns.Length; i++) | ||
| 261 | { | ||
| 262 | var columnDefinition = table.Definition.Columns[i]; | ||
| 263 | |||
| 264 | if (columnDefinition.PrimaryKey) | ||
| 265 | { | ||
| 266 | if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) | ||
| 267 | { | ||
| 268 | row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture); | ||
| 269 | } | ||
| 270 | else | ||
| 271 | { | ||
| 272 | row[i] = primaryKeyParts[primaryKeyPartIndex++]; | ||
| 273 | } | ||
| 274 | } | ||
| 275 | else if (setRequiredFields) | ||
| 276 | { | ||
| 277 | if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) | ||
| 278 | { | ||
| 279 | row[i] = 1; | ||
| 280 | } | ||
| 281 | else if (ColumnType.Object == columnDefinition.Type) | ||
| 282 | { | ||
| 283 | if (null == this.EmptyFile) | ||
| 284 | { | ||
| 285 | this.EmptyFile = Path.Combine(this.IntermediateFolder, ".empty"); | ||
| 286 | using (var fileStream = File.Create(this.EmptyFile)) | ||
| 287 | { | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | row[i] = this.EmptyFile; | ||
| 292 | } | ||
| 293 | else | ||
| 294 | { | ||
| 295 | row[i] = "1"; | ||
| 296 | } | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | return row; | ||
| 301 | } | ||
| 302 | |||
| 303 | private void GenerateDatabase(WindowsInstallerData output, string databaseFile) | ||
| 304 | { | ||
| 305 | var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); | ||
| 306 | command.Execute(); | ||
| 307 | } | ||
| 308 | } | ||
| 309 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs new file mode 100644 index 00000000..0c15ad05 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | internal static class WindowsInstallerBackendErrors | ||
| 8 | { | ||
| 9 | //public static Message ReplaceThisWithTheFirstError(SourceLineNumber sourceLineNumbers) | ||
| 10 | //{ | ||
| 11 | // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstError, "format string", arg1, arg2); | ||
| 12 | //} | ||
| 13 | |||
| 14 | private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) | ||
| 15 | { | ||
| 16 | return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); | ||
| 17 | } | ||
| 18 | |||
| 19 | public enum Ids | ||
| 20 | { | ||
| 21 | // ReplaceThisWithTheFirstError = 7500, | ||
| 22 | } // last available is 7999. 8000 is BurnBackendErrors. | ||
| 23 | } | ||
| 24 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs new file mode 100644 index 00000000..f72acb21 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset.Extensibility; | ||
| 8 | |||
| 9 | internal class WindowsInstallerBackendFactory : IBackendFactory | ||
| 10 | { | ||
| 11 | public bool TryCreateBackend(string outputType, string outputFile, out IBackend backend) | ||
| 12 | { | ||
| 13 | if (String.IsNullOrEmpty(outputType)) | ||
| 14 | { | ||
| 15 | outputType = Path.GetExtension(outputFile); | ||
| 16 | } | ||
| 17 | |||
| 18 | switch (outputType?.ToLowerInvariant()) | ||
| 19 | { | ||
| 20 | case "module": | ||
| 21 | case ".msm": | ||
| 22 | backend = new MsmBackend(); | ||
| 23 | return true; | ||
| 24 | |||
| 25 | case "msipackage": | ||
| 26 | case "package": | ||
| 27 | case "product": | ||
| 28 | case ".msi": | ||
| 29 | backend = new MsiBackend(); | ||
| 30 | return true; | ||
| 31 | |||
| 32 | case "patch": | ||
| 33 | case ".msp": | ||
| 34 | backend = new MspBackend(); | ||
| 35 | return true; | ||
| 36 | |||
| 37 | //case "patchcreation": | ||
| 38 | //case ".pcp": | ||
| 39 | // return new PatchCreationBackend(); | ||
| 40 | |||
| 41 | case "transform": | ||
| 42 | case ".mst": | ||
| 43 | backend = new MstBackend(); | ||
| 44 | return true; | ||
| 45 | } | ||
| 46 | |||
| 47 | backend = null; | ||
| 48 | return false; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs new file mode 100644 index 00000000..d0986a4d --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | internal static class WindowsInstallerBackendWarnings | ||
| 8 | { | ||
| 9 | //public static Message ReplaceThisWithTheFirstWarning(SourceLineNumber sourceLineNumbers) | ||
| 10 | //{ | ||
| 11 | // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstWarning, "format string", arg1, arg2); | ||
| 12 | //} | ||
| 13 | |||
| 14 | private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) | ||
| 15 | { | ||
| 16 | return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); | ||
| 17 | } | ||
| 18 | |||
| 19 | public enum Ids | ||
| 20 | { | ||
| 21 | // ReplaceThisWithTheFirstWarning = 7100, | ||
| 22 | } // last available is 7499. 7500 is WindowsInstallerBackendErrors. | ||
| 23 | } | ||
| 24 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs new file mode 100644 index 00000000..7b12fc8c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility; | ||
| 7 | |||
| 8 | internal class WindowsInstallerExtensionFactory : IExtensionFactory | ||
| 9 | { | ||
| 10 | public bool TryCreateExtension(Type extensionType, out object extension) | ||
| 11 | { | ||
| 12 | extension = null; | ||
| 13 | |||
| 14 | if (extensionType == typeof(IBackendFactory)) | ||
| 15 | { | ||
| 16 | extension = new WindowsInstallerBackendFactory(); | ||
| 17 | } | ||
| 18 | |||
| 19 | return extension != null; | ||
| 20 | } | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj b/src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj new file mode 100644 index 00000000..b08f337f --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFrameworks>netstandard2.0</TargetFrameworks> | ||
| 7 | <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks> | ||
| 8 | <Description>Core Windows Installer</Description> | ||
| 9 | <Title>WiX Toolset Core Windows Installer</Title> | ||
| 10 | <DebugType>embedded</DebugType> | ||
| 11 | <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
| 12 | <CreateDocumentationFile>true</CreateDocumentationFile> | ||
| 13 | </PropertyGroup> | ||
| 14 | |||
| 15 | <ItemGroup> | ||
| 16 | <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" /> | ||
| 17 | <PackageReference Include="WixToolset.Data" Version="4.0.*" /> | ||
| 18 | <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" /> | ||
| 19 | </ItemGroup> | ||
| 20 | |||
| 21 | <ItemGroup> | ||
| 22 | <PackageReference Include="System.Reflection.Metadata" Version="1.6.0" /> | ||
| 23 | <PackageReference Include="System.Text.Encoding.CodePages" Version="4.6.0" /> | ||
| 24 | </ItemGroup> | ||
| 25 | |||
| 26 | <ItemGroup> | ||
| 27 | <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | ||
| 28 | <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="all" /> | ||
| 29 | </ItemGroup> | ||
| 30 | </Project> | ||
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs new file mode 100644 index 00000000..e686fa49 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.WindowsInstaller | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Core.WindowsInstaller.ExtensibilityServices; | ||
| 8 | using WixToolset.Extensibility.Services; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Extensions methods for adding WindowsInstaller services. | ||
| 12 | /// </summary> | ||
| 13 | public static class WixToolsetCoreServiceProviderExtensions | ||
| 14 | { | ||
| 15 | /// <summary> | ||
| 16 | /// Adds WindowsInstaller services. | ||
| 17 | /// </summary> | ||
| 18 | /// <param name="coreProvider"></param> | ||
| 19 | /// <returns></returns> | ||
| 20 | public static IWixToolsetCoreServiceProvider AddWindowsInstallerBackend(this IWixToolsetCoreServiceProvider coreProvider) | ||
| 21 | { | ||
| 22 | AddServices(coreProvider); | ||
| 23 | |||
| 24 | var extensionManager = coreProvider.GetService<IExtensionManager>(); | ||
| 25 | extensionManager.Add(typeof(WindowsInstallerExtensionFactory).Assembly); | ||
| 26 | |||
| 27 | return coreProvider; | ||
| 28 | } | ||
| 29 | |||
| 30 | private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) | ||
| 31 | { | ||
| 32 | // Singletons. | ||
| 33 | coreProvider.AddService((provider, singletons) => AddSingleton<IWindowsInstallerBackendHelper>(singletons, new WindowsInstallerBackendHelper(provider))); | ||
| 34 | } | ||
| 35 | |||
| 36 | private static T AddSingleton<T>(Dictionary<Type, object> singletons, T service) where T : class | ||
| 37 | { | ||
| 38 | singletons.Add(typeof(T), service); | ||
| 39 | return service; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | } | ||
diff --git a/src/wix/WixToolset.Core.sln b/src/wix/WixToolset.Core.sln new file mode 100644 index 00000000..523c960e --- /dev/null +++ b/src/wix/WixToolset.Core.sln | |||
| @@ -0,0 +1,156 @@ | |||
| 1 | Microsoft Visual Studio Solution File, Format Version 12.00 | ||
| 2 | # Visual Studio 15 | ||
| 3 | VisualStudioVersion = 15.0.27004.2009 | ||
| 4 | MinimumVisualStudioVersion = 15.0.26124.0 | ||
| 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core", "src\WixToolset.Core\WixToolset.Core.csproj", "{0B524850-5B9A-472B-85CC-D25920A1DFE1}" | ||
| 6 | EndProject | ||
| 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.WindowsInstaller", "src\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj", "{5617F2A7-46A0-4D07-B9E0-E982D15641E4}" | ||
| 8 | EndProject | ||
| 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.Burn", "src\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj", "{BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}" | ||
| 10 | EndProject | ||
| 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.ExtensionCache", "src\WixToolset.Core.ExtensionCache\WixToolset.Core.ExtensionCache.csproj", "{A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}" | ||
| 12 | EndProject | ||
| 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{1284331E-BC6C-426D-AAAF-140C0174F875}" | ||
| 14 | EndProject | ||
| 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Extension", "src\test\Example.Extension\Example.Extension.csproj", "{C66C2503-C671-4230-8B48-1D93A8532A28}" | ||
| 16 | EndProject | ||
| 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.CoreIntegration", "src\test\WixToolsetTest.CoreIntegration\WixToolsetTest.CoreIntegration.csproj", "{E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}" | ||
| 18 | EndProject | ||
| 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Core.Burn", "src\test\WixToolsetTest.Core.Burn\WixToolsetTest.Core.Burn.csproj", "{DF63F589-028E-45A1-A212-948FACF1FDCD}" | ||
| 20 | EndProject | ||
| 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.TestPackage", "src\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj", "{853716DB-C02C-41BD-91BC-79CDC0C17D10}" | ||
| 22 | EndProject | ||
| 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompileCoreTestExtensionWixlib", "src\test\CompileCoreTestExtensionWixlib\CompileCoreTestExtensionWixlib.csproj", "{23FC60D7-B101-42F8-9786-DB7A9CD964A2}" | ||
| 24 | EndProject | ||
| 25 | Global | ||
| 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
| 27 | Debug|Any CPU = Debug|Any CPU | ||
| 28 | Debug|x64 = Debug|x64 | ||
| 29 | Debug|x86 = Debug|x86 | ||
| 30 | Release|Any CPU = Release|Any CPU | ||
| 31 | Release|x64 = Release|x64 | ||
| 32 | Release|x86 = Release|x86 | ||
| 33 | EndGlobalSection | ||
| 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
| 35 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| 36 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| 37 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| 38 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.Build.0 = Debug|Any CPU | ||
| 39 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| 40 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.Build.0 = Debug|Any CPU | ||
| 41 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| 42 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| 43 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.ActiveCfg = Release|Any CPU | ||
| 44 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.Build.0 = Release|Any CPU | ||
| 45 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.ActiveCfg = Release|Any CPU | ||
| 46 | {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.Build.0 = Release|Any CPU | ||
| 47 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| 48 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| 49 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| 50 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.Build.0 = Debug|Any CPU | ||
| 51 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| 52 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.Build.0 = Debug|Any CPU | ||
| 53 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| 54 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| 55 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.ActiveCfg = Release|Any CPU | ||
| 56 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.Build.0 = Release|Any CPU | ||
| 57 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.ActiveCfg = Release|Any CPU | ||
| 58 | {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.Build.0 = Release|Any CPU | ||
| 59 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| 60 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| 61 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| 62 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.Build.0 = Debug|Any CPU | ||
| 63 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| 64 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.Build.0 = Debug|Any CPU | ||
| 65 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| 66 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| 67 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.ActiveCfg = Release|Any CPU | ||
| 68 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.Build.0 = Release|Any CPU | ||
| 69 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.ActiveCfg = Release|Any CPU | ||
| 70 | {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.Build.0 = Release|Any CPU | ||
| 71 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| 72 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| 73 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| 74 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.Build.0 = Debug|Any CPU | ||
| 75 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| 76 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.Build.0 = Debug|Any CPU | ||
| 77 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| 78 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| 79 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.ActiveCfg = Release|Any CPU | ||
| 80 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.Build.0 = Release|Any CPU | ||
| 81 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.ActiveCfg = Release|Any CPU | ||
| 82 | {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.Build.0 = Release|Any CPU | ||
| 83 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| 84 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| 85 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| 86 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.Build.0 = Debug|Any CPU | ||
| 87 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| 88 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.Build.0 = Debug|Any CPU | ||
| 89 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| 90 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| 91 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.ActiveCfg = Release|Any CPU | ||
| 92 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.Build.0 = Release|Any CPU | ||
| 93 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.ActiveCfg = Release|Any CPU | ||
| 94 | {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.Build.0 = Release|Any CPU | ||
| 95 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| 96 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| 97 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| 98 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.Build.0 = Debug|Any CPU | ||
| 99 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| 100 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.Build.0 = Debug|Any CPU | ||
| 101 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| 102 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| 103 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.ActiveCfg = Release|Any CPU | ||
| 104 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.Build.0 = Release|Any CPU | ||
| 105 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.ActiveCfg = Release|Any CPU | ||
| 106 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.Build.0 = Release|Any CPU | ||
| 107 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| 108 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| 109 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| 110 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.Build.0 = Debug|Any CPU | ||
| 111 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| 112 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.Build.0 = Debug|Any CPU | ||
| 113 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| 114 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| 115 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.ActiveCfg = Release|Any CPU | ||
| 116 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.Build.0 = Release|Any CPU | ||
| 117 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.ActiveCfg = Release|Any CPU | ||
| 118 | {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.Build.0 = Release|Any CPU | ||
| 119 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| 120 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| 121 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| 122 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.Build.0 = Debug|Any CPU | ||
| 123 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| 124 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.Build.0 = Debug|Any CPU | ||
| 125 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| 126 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| 127 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.ActiveCfg = Release|Any CPU | ||
| 128 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.Build.0 = Release|Any CPU | ||
| 129 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.ActiveCfg = Release|Any CPU | ||
| 130 | {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.Build.0 = Release|Any CPU | ||
| 131 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| 132 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| 133 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| 134 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.Build.0 = Debug|Any CPU | ||
| 135 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| 136 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.Build.0 = Debug|Any CPU | ||
| 137 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| 138 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| 139 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.ActiveCfg = Release|Any CPU | ||
| 140 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.Build.0 = Release|Any CPU | ||
| 141 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.ActiveCfg = Release|Any CPU | ||
| 142 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.Build.0 = Release|Any CPU | ||
| 143 | EndGlobalSection | ||
| 144 | GlobalSection(SolutionProperties) = preSolution | ||
| 145 | HideSolutionNode = FALSE | ||
| 146 | EndGlobalSection | ||
| 147 | GlobalSection(NestedProjects) = preSolution | ||
| 148 | {C66C2503-C671-4230-8B48-1D93A8532A28} = {1284331E-BC6C-426D-AAAF-140C0174F875} | ||
| 149 | {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B} = {1284331E-BC6C-426D-AAAF-140C0174F875} | ||
| 150 | {DF63F589-028E-45A1-A212-948FACF1FDCD} = {1284331E-BC6C-426D-AAAF-140C0174F875} | ||
| 151 | {23FC60D7-B101-42F8-9786-DB7A9CD964A2} = {1284331E-BC6C-426D-AAAF-140C0174F875} | ||
| 152 | EndGlobalSection | ||
| 153 | GlobalSection(ExtensibilityGlobals) = postSolution | ||
| 154 | SolutionGuid = {BB8820D5-723D-426D-B4A0-4D221603C5FA} | ||
| 155 | EndGlobalSection | ||
| 156 | EndGlobal | ||
diff --git a/src/wix/WixToolset.Core.v3.ncrunchsolution b/src/wix/WixToolset.Core.v3.ncrunchsolution new file mode 100644 index 00000000..10420ac9 --- /dev/null +++ b/src/wix/WixToolset.Core.v3.ncrunchsolution | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <SolutionConfiguration> | ||
| 2 | <Settings> | ||
| 3 | <AllowParallelTestExecution>True</AllowParallelTestExecution> | ||
| 4 | <SolutionConfigured>True</SolutionConfigured> | ||
| 5 | </Settings> | ||
| 6 | </SolutionConfiguration> \ No newline at end of file | ||
diff --git a/src/wix/WixToolset.Core/Bind/DelayedField.cs b/src/wix/WixToolset.Core/Bind/DelayedField.cs new file mode 100644 index 00000000..25641516 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/DelayedField.cs | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Bind | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Holds a symbol and field that contain binder variables, which need to be resolved | ||
| 10 | /// later, once the files have been resolved. | ||
| 11 | /// </summary> | ||
| 12 | internal class DelayedField : IDelayedField | ||
| 13 | { | ||
| 14 | /// <summary> | ||
| 15 | /// Creates a delayed field. | ||
| 16 | /// </summary> | ||
| 17 | /// <param name="symbol">Symbol for the field.</param> | ||
| 18 | /// <param name="field">Field needing further resolution.</param> | ||
| 19 | public DelayedField(IntermediateSymbol symbol, IntermediateField field) | ||
| 20 | { | ||
| 21 | this.Symbol = symbol; | ||
| 22 | this.Field = field; | ||
| 23 | } | ||
| 24 | |||
| 25 | /// <summary> | ||
| 26 | /// The row containing the field. | ||
| 27 | /// </summary> | ||
| 28 | public IntermediateSymbol Symbol { get; } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// The field needing further resolving. | ||
| 32 | /// </summary> | ||
| 33 | public IntermediateField Field { get; } | ||
| 34 | } | ||
| 35 | } | ||
diff --git a/src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs b/src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs new file mode 100644 index 00000000..b27cdfee --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | internal class ExpectedExtractFile : IExpectedExtractFile | ||
| 9 | { | ||
| 10 | public Uri Uri { get; set; } | ||
| 11 | |||
| 12 | public string EmbeddedFileId { get; set; } | ||
| 13 | |||
| 14 | public string OutputPath { get; set; } | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs new file mode 100644 index 00000000..a0798e62 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Security.Cryptography; | ||
| 11 | using System.Text; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// Internal helper class used to extract embedded files. | ||
| 15 | /// </summary> | ||
| 16 | internal class ExtractEmbeddedFiles | ||
| 17 | { | ||
| 18 | private readonly Dictionary<Uri, SortedList<string, string>> filesWithEmbeddedFiles = new Dictionary<Uri, SortedList<string, string>>(); | ||
| 19 | |||
| 20 | public IEnumerable<Uri> Uris => this.filesWithEmbeddedFiles.Keys; | ||
| 21 | |||
| 22 | /// <summary> | ||
| 23 | /// Adds an embedded file index to track and returns the path where the embedded file will be extracted. Duplicates will return the same extract path. | ||
| 24 | /// </summary> | ||
| 25 | /// <param name="uri">Uri to file containing the embedded files.</param> | ||
| 26 | /// <param name="embeddedFileId">Id of the embedded file to extract.</param> | ||
| 27 | /// <param name="extractFolder">Folder where extracted files should be placed.</param> | ||
| 28 | /// <returns>The extract path for the embedded file.</returns> | ||
| 29 | public string AddEmbeddedFileToExtract(Uri uri, string embeddedFileId, string extractFolder) | ||
| 30 | { | ||
| 31 | // If the uri to the file that contains the embedded file does not already have embedded files | ||
| 32 | // being extracted, create the dictionary to track that. | ||
| 33 | if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) | ||
| 34 | { | ||
| 35 | extracts = new SortedList<string, string>(StringComparer.OrdinalIgnoreCase); | ||
| 36 | this.filesWithEmbeddedFiles.Add(uri, extracts); | ||
| 37 | } | ||
| 38 | |||
| 39 | // If the embedded file is not already tracked in the dictionary of extracts, add it. | ||
| 40 | if (!extracts.TryGetValue(embeddedFileId, out var extractPath)) | ||
| 41 | { | ||
| 42 | var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); | ||
| 43 | var unique = this.HashUri(uri.AbsoluteUri); | ||
| 44 | var extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileId); | ||
| 45 | |||
| 46 | extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName)); | ||
| 47 | extracts.Add(embeddedFileId, extractPath); | ||
| 48 | } | ||
| 49 | |||
| 50 | return extractPath; | ||
| 51 | } | ||
| 52 | |||
| 53 | public IReadOnlyList<ExpectedExtractFile> GetExpectedEmbeddedFiles() | ||
| 54 | { | ||
| 55 | var files = new List<ExpectedExtractFile>(); | ||
| 56 | |||
| 57 | foreach (var uriWithExtracts in this.filesWithEmbeddedFiles) | ||
| 58 | { | ||
| 59 | foreach (var extracts in uriWithExtracts.Value) | ||
| 60 | { | ||
| 61 | files.Add(new ExpectedExtractFile | ||
| 62 | { | ||
| 63 | Uri = uriWithExtracts.Key, | ||
| 64 | EmbeddedFileId = extracts.Key, | ||
| 65 | OutputPath = extracts.Value, | ||
| 66 | }); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | return files; | ||
| 71 | } | ||
| 72 | |||
| 73 | public IEnumerable<ExpectedExtractFile> GetExtractFilesForUri(Uri uri) | ||
| 74 | { | ||
| 75 | if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) | ||
| 76 | { | ||
| 77 | extracts = new SortedList<string, string>(StringComparer.OrdinalIgnoreCase); | ||
| 78 | } | ||
| 79 | |||
| 80 | return extracts.Select(e => new ExpectedExtractFile { Uri = uri, EmbeddedFileId = e.Key, OutputPath = e.Value }); | ||
| 81 | } | ||
| 82 | |||
| 83 | private string HashUri(string uri) | ||
| 84 | { | ||
| 85 | using (SHA1 sha1 = new SHA1CryptoServiceProvider()) | ||
| 86 | { | ||
| 87 | var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(uri)); | ||
| 88 | return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
diff --git a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs new file mode 100644 index 00000000..ec2d8896 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility.Data; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | internal class ExtractEmbeddedFilesCommand | ||
| 13 | { | ||
| 14 | public ExtractEmbeddedFilesCommand(IBackendHelper backendHelper, IEnumerable<IExpectedExtractFile> embeddedFiles) | ||
| 15 | { | ||
| 16 | this.BackendHelper = backendHelper; | ||
| 17 | this.FilesWithEmbeddedFiles = embeddedFiles; | ||
| 18 | } | ||
| 19 | |||
| 20 | public IReadOnlyList<ITrackedFile> TrackedFiles { get; private set; } | ||
| 21 | |||
| 22 | private IBackendHelper BackendHelper { get; } | ||
| 23 | |||
| 24 | private IEnumerable<IExpectedExtractFile> FilesWithEmbeddedFiles { get; } | ||
| 25 | |||
| 26 | public void Execute() | ||
| 27 | { | ||
| 28 | var trackedFiles = new List<ITrackedFile>(); | ||
| 29 | var group = this.FilesWithEmbeddedFiles.GroupBy(e => e.Uri); | ||
| 30 | |||
| 31 | foreach (var expectedEmbeddedFileByUri in group) | ||
| 32 | { | ||
| 33 | var baseUri = expectedEmbeddedFileByUri.Key; | ||
| 34 | |||
| 35 | using (var wixout = WixOutput.Read(baseUri)) | ||
| 36 | { | ||
| 37 | var uniqueIds = new SortedSet<string>(StringComparer.OrdinalIgnoreCase); | ||
| 38 | |||
| 39 | foreach (var embeddedFile in expectedEmbeddedFileByUri) | ||
| 40 | { | ||
| 41 | if (uniqueIds.Add(embeddedFile.EmbeddedFileId)) | ||
| 42 | { | ||
| 43 | wixout.ExtractEmbeddedFile(embeddedFile.EmbeddedFileId, embeddedFile.OutputPath); | ||
| 44 | trackedFiles.Add(this.BackendHelper.TrackFile(embeddedFile.OutputPath, TrackedFileType.Temporary)); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | this.TrackedFiles = trackedFiles; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
diff --git a/src/wix/WixToolset.Core/Bind/FileResolver.cs b/src/wix/WixToolset.Core/Bind/FileResolver.cs new file mode 100644 index 00000000..eb878239 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/FileResolver.cs | |||
| @@ -0,0 +1,207 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | |||
| 13 | internal class FileResolver | ||
| 14 | { | ||
| 15 | private const string BindPathOpenString = "!(bindpath."; | ||
| 16 | |||
| 17 | private FileResolver(IEnumerable<IBindPath> bindPaths) | ||
| 18 | { | ||
| 19 | this.BindPaths = (bindPaths ?? Array.Empty<IBindPath>()).ToLookup(b => b.Stage); | ||
| 20 | this.RebaseTarget = this.BindPaths[BindStage.Target].Any(); | ||
| 21 | this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any(); | ||
| 22 | } | ||
| 23 | |||
| 24 | public FileResolver(IEnumerable<IBindPath> bindPaths, IEnumerable<IResolverExtension> extensions) : this(bindPaths) | ||
| 25 | { | ||
| 26 | this.ResolverExtensions = extensions ?? Array.Empty<IResolverExtension>(); | ||
| 27 | } | ||
| 28 | |||
| 29 | public FileResolver(IEnumerable<IBindPath> bindPaths, IEnumerable<ILibrarianExtension> extensions) : this(bindPaths) | ||
| 30 | { | ||
| 31 | this.LibrarianExtensions = extensions ?? Array.Empty<ILibrarianExtension>(); | ||
| 32 | } | ||
| 33 | |||
| 34 | private ILookup<BindStage, IBindPath> BindPaths { get; } | ||
| 35 | |||
| 36 | public bool RebaseTarget { get; } | ||
| 37 | |||
| 38 | public bool RebaseUpdated { get; } | ||
| 39 | |||
| 40 | private IEnumerable<IResolverExtension> ResolverExtensions { get; } | ||
| 41 | |||
| 42 | private IEnumerable<ILibrarianExtension> LibrarianExtensions { get; } | ||
| 43 | |||
| 44 | public string Resolve(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string source) | ||
| 45 | { | ||
| 46 | var checkedPaths = new List<string>(); | ||
| 47 | |||
| 48 | foreach (var extension in this.LibrarianExtensions) | ||
| 49 | { | ||
| 50 | var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source); | ||
| 51 | |||
| 52 | if (resolved?.CheckedPaths != null) | ||
| 53 | { | ||
| 54 | checkedPaths.AddRange(resolved.CheckedPaths); | ||
| 55 | } | ||
| 56 | |||
| 57 | if (!String.IsNullOrEmpty(resolved?.Path)) | ||
| 58 | { | ||
| 59 | return resolved?.Path; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, BindStage.Normal, checkedPaths); | ||
| 64 | } | ||
| 65 | |||
| 66 | /// <summary> | ||
| 67 | /// Resolves the source path of a file using binder extensions. | ||
| 68 | /// </summary> | ||
| 69 | /// <param name="source">Original source value.</param> | ||
| 70 | /// <param name="symbolDefinition">Optional type of source file being resolved.</param> | ||
| 71 | /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param> | ||
| 72 | /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param> | ||
| 73 | /// <param name="alreadyCheckedPaths">Optional collection of paths already checked.</param> | ||
| 74 | /// <returns>Should return a valid path for the stream to be imported.</returns> | ||
| 75 | public string ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable<string> alreadyCheckedPaths = null) | ||
| 76 | { | ||
| 77 | var checkedPaths = new List<string>(); | ||
| 78 | |||
| 79 | if (alreadyCheckedPaths != null) | ||
| 80 | { | ||
| 81 | checkedPaths.AddRange(alreadyCheckedPaths); | ||
| 82 | } | ||
| 83 | |||
| 84 | foreach (var extension in this.ResolverExtensions) | ||
| 85 | { | ||
| 86 | var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage); | ||
| 87 | |||
| 88 | if (resolved?.CheckedPaths != null) | ||
| 89 | { | ||
| 90 | checkedPaths.AddRange(resolved.CheckedPaths); | ||
| 91 | } | ||
| 92 | |||
| 93 | if (!String.IsNullOrEmpty(resolved?.Path)) | ||
| 94 | { | ||
| 95 | return resolved?.Path; | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindStage, checkedPaths); | ||
| 100 | } | ||
| 101 | |||
| 102 | private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, List<string> checkedPaths) | ||
| 103 | { | ||
| 104 | string resolved = null; | ||
| 105 | |||
| 106 | // If the file exists, we're good to go. | ||
| 107 | checkedPaths.Add(source); | ||
| 108 | if (CheckFileExists(source)) | ||
| 109 | { | ||
| 110 | resolved = source; | ||
| 111 | } | ||
| 112 | else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist. | ||
| 113 | { | ||
| 114 | resolved = null; | ||
| 115 | } | ||
| 116 | else // not a rooted path so let's try applying all the different source resolution options. | ||
| 117 | { | ||
| 118 | var bindName = String.Empty; | ||
| 119 | var path = source; | ||
| 120 | var pathWithoutSourceDir = String.Empty; | ||
| 121 | |||
| 122 | if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) | ||
| 123 | { | ||
| 124 | var closeParen = source.IndexOf(')', BindPathOpenString.Length); | ||
| 125 | |||
| 126 | if (-1 != closeParen) | ||
| 127 | { | ||
| 128 | bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length); | ||
| 129 | path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace. | ||
| 130 | path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted. | ||
| 131 | } | ||
| 132 | } | ||
| 133 | else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal)) | ||
| 134 | { | ||
| 135 | pathWithoutSourceDir = path.Substring(10); | ||
| 136 | } | ||
| 137 | |||
| 138 | var bindPaths = this.BindPaths[bindStage]; | ||
| 139 | |||
| 140 | foreach (var bindPath in bindPaths) | ||
| 141 | { | ||
| 142 | if (String.IsNullOrEmpty(bindName)) | ||
| 143 | { | ||
| 144 | if (String.IsNullOrEmpty(bindPath.Name)) | ||
| 145 | { | ||
| 146 | if (!String.IsNullOrEmpty(pathWithoutSourceDir)) | ||
| 147 | { | ||
| 148 | var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); | ||
| 149 | |||
| 150 | checkedPaths.Add(filePath); | ||
| 151 | if (CheckFileExists(filePath)) | ||
| 152 | { | ||
| 153 | resolved = filePath; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | if (String.IsNullOrEmpty(resolved)) | ||
| 158 | { | ||
| 159 | var filePath = Path.Combine(bindPath.Path, path); | ||
| 160 | |||
| 161 | checkedPaths.Add(filePath); | ||
| 162 | if (CheckFileExists(filePath)) | ||
| 163 | { | ||
| 164 | resolved = filePath; | ||
| 165 | } | ||
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase)) | ||
| 170 | { | ||
| 171 | var filePath = Path.Combine(bindPath.Path, path); | ||
| 172 | |||
| 173 | checkedPaths.Add(filePath); | ||
| 174 | if (CheckFileExists(filePath)) | ||
| 175 | { | ||
| 176 | resolved = filePath; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | if (!String.IsNullOrEmpty(resolved)) | ||
| 181 | { | ||
| 182 | break; | ||
| 183 | } | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | if (null == resolved) | ||
| 188 | { | ||
| 189 | throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition.Name, checkedPaths)); | ||
| 190 | } | ||
| 191 | |||
| 192 | return resolved; | ||
| 193 | } | ||
| 194 | |||
| 195 | private static bool CheckFileExists(string path) | ||
| 196 | { | ||
| 197 | try | ||
| 198 | { | ||
| 199 | return File.Exists(path); | ||
| 200 | } | ||
| 201 | catch (ArgumentException) | ||
| 202 | { | ||
| 203 | throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
diff --git a/src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs new file mode 100644 index 00000000..4ad8f764 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs | |||
| @@ -0,0 +1,164 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Text; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// Resolves the fields which had variables that needed to be resolved after the file information | ||
| 15 | /// was loaded. | ||
| 16 | /// </summary> | ||
| 17 | internal class ResolveDelayedFieldsCommand | ||
| 18 | { | ||
| 19 | /// <summary> | ||
| 20 | /// Resolve delayed fields. | ||
| 21 | /// </summary> | ||
| 22 | /// <param name="messaging"></param> | ||
| 23 | /// <param name="delayedFields">The fields which had resolution delayed.</param> | ||
| 24 | /// <param name="variableCache">The cached variable values used when resolving delayed fields.</param> | ||
| 25 | public ResolveDelayedFieldsCommand(IMessaging messaging, IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache) | ||
| 26 | { | ||
| 27 | this.Messaging = messaging; | ||
| 28 | this.DelayedFields = delayedFields; | ||
| 29 | this.VariableCache = variableCache; | ||
| 30 | } | ||
| 31 | |||
| 32 | private IMessaging Messaging { get; } | ||
| 33 | |||
| 34 | private IEnumerable<IDelayedField> DelayedFields { get;} | ||
| 35 | |||
| 36 | private IDictionary<string, string> VariableCache { get; } | ||
| 37 | |||
| 38 | public void Execute() | ||
| 39 | { | ||
| 40 | var deferredFields = new List<IDelayedField>(); | ||
| 41 | |||
| 42 | foreach (var delayedField in this.DelayedFields) | ||
| 43 | { | ||
| 44 | try | ||
| 45 | { | ||
| 46 | var propertySymbol = delayedField.Symbol; | ||
| 47 | |||
| 48 | // process properties first in case they refer to other binder variables | ||
| 49 | if (delayedField.Symbol.Definition.Type == SymbolDefinitionType.Property) | ||
| 50 | { | ||
| 51 | var value = this.ResolveDelayedVariables(propertySymbol.SourceLineNumbers, delayedField.Field.AsString()); | ||
| 52 | |||
| 53 | // update the variable cache with the new value | ||
| 54 | var key = String.Concat("property.", propertySymbol.Id.Id); | ||
| 55 | this.VariableCache[key] = value; | ||
| 56 | |||
| 57 | // update the field data | ||
| 58 | delayedField.Field.Set(value); | ||
| 59 | } | ||
| 60 | else | ||
| 61 | { | ||
| 62 | deferredFields.Add(delayedField); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | catch (WixException we) | ||
| 66 | { | ||
| 67 | this.Messaging.Write(we.Error); | ||
| 68 | continue; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | // add specialization for ProductVersion fields | ||
| 73 | var keyProductVersion = "property.ProductVersion"; | ||
| 74 | if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out var productVersion)) | ||
| 75 | { | ||
| 76 | // Don't add the variable if it already exists (developer defined a property with the same name). | ||
| 77 | var fieldKey = String.Concat(keyProductVersion, ".Major"); | ||
| 78 | if (!this.VariableCache.ContainsKey(fieldKey)) | ||
| 79 | { | ||
| 80 | this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture); | ||
| 81 | } | ||
| 82 | |||
| 83 | fieldKey = String.Concat(keyProductVersion, ".Minor"); | ||
| 84 | if (!this.VariableCache.ContainsKey(fieldKey)) | ||
| 85 | { | ||
| 86 | this.VariableCache[fieldKey] = productVersion.Minor.ToString(CultureInfo.InvariantCulture); | ||
| 87 | } | ||
| 88 | |||
| 89 | fieldKey = String.Concat(keyProductVersion, ".Build"); | ||
| 90 | if (!this.VariableCache.ContainsKey(fieldKey)) | ||
| 91 | { | ||
| 92 | this.VariableCache[fieldKey] = productVersion.Build.ToString(CultureInfo.InvariantCulture); | ||
| 93 | } | ||
| 94 | |||
| 95 | fieldKey = String.Concat(keyProductVersion, ".Revision"); | ||
| 96 | if (!this.VariableCache.ContainsKey(fieldKey)) | ||
| 97 | { | ||
| 98 | this.VariableCache[fieldKey] = productVersion.Revision.ToString(CultureInfo.InvariantCulture); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | // process the remaining fields in case they refer to property binder variables | ||
| 103 | foreach (var delayedField in deferredFields) | ||
| 104 | { | ||
| 105 | try | ||
| 106 | { | ||
| 107 | var value = this.ResolveDelayedVariables(delayedField.Symbol.SourceLineNumbers, delayedField.Field.AsString()); | ||
| 108 | delayedField.Field.Set(value); | ||
| 109 | } | ||
| 110 | catch (WixException we) | ||
| 111 | { | ||
| 112 | this.Messaging.Write(we.Error); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | private string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value) | ||
| 118 | { | ||
| 119 | var start = 0; | ||
| 120 | |||
| 121 | while (Common.TryParseWixVariable(value, start, out var parsed)) | ||
| 122 | { | ||
| 123 | if (parsed.Namespace == "bind") | ||
| 124 | { | ||
| 125 | var key = String.Concat(parsed.Name, ".", parsed.Scope); | ||
| 126 | |||
| 127 | if (!this.VariableCache.TryGetValue(key, out var resolvedValue)) | ||
| 128 | { | ||
| 129 | resolvedValue = parsed.DefaultValue; | ||
| 130 | } | ||
| 131 | |||
| 132 | // insert the resolved value if it was found or display an error | ||
| 133 | if (null != resolvedValue) | ||
| 134 | { | ||
| 135 | if (parsed.Index == 0 && parsed.Length == value.Length) | ||
| 136 | { | ||
| 137 | value = resolvedValue; | ||
| 138 | } | ||
| 139 | else | ||
| 140 | { | ||
| 141 | var sb = new StringBuilder(value); | ||
| 142 | sb.Remove(parsed.Index, parsed.Length); | ||
| 143 | sb.Insert(parsed.Index, resolvedValue); | ||
| 144 | value = sb.ToString(); | ||
| 145 | } | ||
| 146 | |||
| 147 | start = parsed.Index; | ||
| 148 | } | ||
| 149 | else | ||
| 150 | { | ||
| 151 | this.Messaging.Write(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value)); | ||
| 152 | break; | ||
| 153 | } | ||
| 154 | } | ||
| 155 | else | ||
| 156 | { | ||
| 157 | start = parsed.Index + parsed.Length; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | return value; | ||
| 162 | } | ||
| 163 | } | ||
| 164 | } | ||
diff --git a/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs new file mode 100644 index 00000000..794208e5 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs | |||
| @@ -0,0 +1,276 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Resolve source fields in the tables included in the output | ||
| 16 | /// </summary> | ||
| 17 | internal class ResolveFieldsCommand | ||
| 18 | { | ||
| 19 | public IMessaging Messaging { private get; set; } | ||
| 20 | |||
| 21 | public bool BuildingPatch { private get; set; } | ||
| 22 | |||
| 23 | public IVariableResolver VariableResolver { private get; set; } | ||
| 24 | |||
| 25 | public IEnumerable<IBindPath> BindPaths { private get; set; } | ||
| 26 | |||
| 27 | public IEnumerable<IResolverExtension> Extensions { private get; set; } | ||
| 28 | |||
| 29 | public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } | ||
| 30 | |||
| 31 | public string IntermediateFolder { private get; set; } | ||
| 32 | |||
| 33 | public Intermediate Intermediate { private get; set; } | ||
| 34 | |||
| 35 | public bool SupportDelayedResolution { private get; set; } | ||
| 36 | |||
| 37 | public bool AllowUnresolvedVariables { private get; set; } | ||
| 38 | |||
| 39 | public IReadOnlyCollection<DelayedField> DelayedFields { get; private set; } | ||
| 40 | |||
| 41 | public void Execute() | ||
| 42 | { | ||
| 43 | var delayedFields = this.SupportDelayedResolution ? new List<DelayedField>() : null; | ||
| 44 | |||
| 45 | var fileResolver = new FileResolver(this.BindPaths, this.Extensions); | ||
| 46 | |||
| 47 | // Build the column lookup only when needed. | ||
| 48 | Dictionary<string, WixCustomTableColumnSymbol> customColumnsById = null; | ||
| 49 | |||
| 50 | foreach (var sections in this.Intermediate.Sections) | ||
| 51 | { | ||
| 52 | foreach (var symbol in sections.Symbols) | ||
| 53 | { | ||
| 54 | foreach (var field in symbol.Fields) | ||
| 55 | { | ||
| 56 | if (field.IsNull()) | ||
| 57 | { | ||
| 58 | continue; | ||
| 59 | } | ||
| 60 | |||
| 61 | var fieldType = field.Type; | ||
| 62 | |||
| 63 | // Custom table cells require an extra look up to the column definition as the | ||
| 64 | // cell's data type is always a string (because strings can store anything) but | ||
| 65 | // the column definition may be more specific. | ||
| 66 | if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell) | ||
| 67 | { | ||
| 68 | // We only care about the Data in a CustomTable cell. | ||
| 69 | if (field.Name != nameof(WixCustomTableCellSymbolFields.Data)) | ||
| 70 | { | ||
| 71 | continue; | ||
| 72 | } | ||
| 73 | |||
| 74 | if (customColumnsById == null) | ||
| 75 | { | ||
| 76 | customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType<WixCustomTableColumnSymbol>()).ToDictionary(t => t.Id.Id); | ||
| 77 | } | ||
| 78 | |||
| 79 | if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn)) | ||
| 80 | { | ||
| 81 | fieldType = customColumn.Type; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | // Check to make sure we're in a scenario where we can handle variable resolution. | ||
| 86 | if (null != delayedFields) | ||
| 87 | { | ||
| 88 | // resolve localization and wix variables | ||
| 89 | if (fieldType == IntermediateFieldType.String) | ||
| 90 | { | ||
| 91 | var original = field.AsString(); | ||
| 92 | if (!String.IsNullOrEmpty(original)) | ||
| 93 | { | ||
| 94 | var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables); | ||
| 95 | if (resolution.UpdatedValue) | ||
| 96 | { | ||
| 97 | field.Set(resolution.Value); | ||
| 98 | } | ||
| 99 | |||
| 100 | if (resolution.DelayedResolve) | ||
| 101 | { | ||
| 102 | delayedFields.Add(new DelayedField(symbol, field)); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | // Move to next symbol if we've hit an error resolving variables. | ||
| 109 | if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. | ||
| 110 | { | ||
| 111 | continue; | ||
| 112 | } | ||
| 113 | |||
| 114 | // Resolve file paths | ||
| 115 | if (fieldType == IntermediateFieldType.Path) | ||
| 116 | { | ||
| 117 | this.ResolvePathField(fileResolver, symbol, field); | ||
| 118 | |||
| 119 | #if TODO_PATCHING | ||
| 120 | if (null != objectField.PreviousData) | ||
| 121 | { | ||
| 122 | objectField.PreviousData = this.BindVariableResolver.ResolveVariables(symbol.SourceLineNumbers, objectField.PreviousData, false, out isDefault); | ||
| 123 | |||
| 124 | if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. | ||
| 125 | { | ||
| 126 | // file is compressed in a cabinet (and not modified above) | ||
| 127 | if (objectField.PreviousEmbeddedFileIndex.HasValue && isDefault) | ||
| 128 | { | ||
| 129 | // when loading transforms from disk, PreviousBaseUri may not have been set | ||
| 130 | if (null == objectField.PreviousBaseUri) | ||
| 131 | { | ||
| 132 | objectField.PreviousBaseUri = objectField.BaseUri; | ||
| 133 | } | ||
| 134 | |||
| 135 | string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder); | ||
| 136 | |||
| 137 | // set the path to the file once its extracted from the cabinet | ||
| 138 | objectField.PreviousData = extractPath; | ||
| 139 | } | ||
| 140 | else if (null != objectField.PreviousData) // non-compressed file (or localized value) | ||
| 141 | { | ||
| 142 | try | ||
| 143 | { | ||
| 144 | if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) | ||
| 145 | { | ||
| 146 | // resolve the path to the file | ||
| 147 | objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Normal); | ||
| 148 | } | ||
| 149 | else | ||
| 150 | { | ||
| 151 | if (fileResolver.RebaseTarget) | ||
| 152 | { | ||
| 153 | // if -bt is used, it come here | ||
| 154 | // Try to use the original unresolved source from either target build or update build | ||
| 155 | // If both target and updated are of old wixpdb, it behaves the same as today, no re-base logic here | ||
| 156 | // If target is old version and updated is new version, it uses unresolved path from updated build | ||
| 157 | // If both target and updated are of new versions, it uses unresolved path from target build | ||
| 158 | if (null != objectField.UnresolvedPreviousData || null != objectField.UnresolvedData) | ||
| 159 | { | ||
| 160 | objectField.PreviousData = objectField.UnresolvedPreviousData ?? objectField.UnresolvedData; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | // resolve the path to the file | ||
| 165 | objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Target); | ||
| 166 | |||
| 167 | } | ||
| 168 | } | ||
| 169 | catch (WixFileNotFoundException) | ||
| 170 | { | ||
| 171 | // display the error with source line information | ||
| 172 | Messaging.Instance.Write(WixErrors.FileNotFound(symbol.SourceLineNumbers, (string)objectField.PreviousData)); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
| 176 | } | ||
| 177 | #endif | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | this.DelayedFields = delayedFields; | ||
| 184 | } | ||
| 185 | |||
| 186 | private void ResolvePathField(FileResolver fileResolver, IntermediateSymbol symbol, IntermediateField field) | ||
| 187 | { | ||
| 188 | var fieldValue = field.AsPath(); | ||
| 189 | var originalFieldPath = fieldValue.Path; | ||
| 190 | |||
| 191 | #if TODO_PATCHING | ||
| 192 | // Skip file resolution if the file is to be deleted. | ||
| 193 | if (RowOperation.Delete == symbol.Operation) | ||
| 194 | { | ||
| 195 | continue; | ||
| 196 | } | ||
| 197 | #endif | ||
| 198 | |||
| 199 | // If the file is embedded and if the previous value has a bind variable in the path | ||
| 200 | // which gets modified by resolving the previous value again then switch to that newly | ||
| 201 | // resolved path instead of using the embedded file. | ||
| 202 | if (fieldValue.Embed && field.PreviousValue != null) | ||
| 203 | { | ||
| 204 | var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, field.PreviousValue.AsString(), errorOnUnknown: false); | ||
| 205 | |||
| 206 | if (resolution.UpdatedValue && !resolution.IsDefault) | ||
| 207 | { | ||
| 208 | fieldValue = new IntermediateFieldPathValue { Path = resolution.Value }; | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | // If we're still using the embedded file. | ||
| 213 | if (fieldValue.Embed) | ||
| 214 | { | ||
| 215 | // Set the path to the embedded file once where it will be extracted. | ||
| 216 | var extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileToExtract(fieldValue.BaseUri, fieldValue.Path, this.IntermediateFolder); | ||
| 217 | |||
| 218 | field.Set(extractPath); | ||
| 219 | } | ||
| 220 | else if (fieldValue.Path != null) | ||
| 221 | { | ||
| 222 | try | ||
| 223 | { | ||
| 224 | var resolvedPath = fieldValue.Path; | ||
| 225 | |||
| 226 | if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe) | ||
| 227 | { | ||
| 228 | #if TODO_PATCHING | ||
| 229 | // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file | ||
| 230 | if (null == objectField.UnresolvedData) | ||
| 231 | { | ||
| 232 | objectField.UnresolvedData = (string)objectField.Data; | ||
| 233 | } | ||
| 234 | #endif | ||
| 235 | resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); | ||
| 236 | } | ||
| 237 | else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic) | ||
| 238 | { | ||
| 239 | resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); | ||
| 240 | } | ||
| 241 | #if TODO_PATCHING | ||
| 242 | else // Re-base binding path scenario caused by pyro.exe -bt -bu | ||
| 243 | { | ||
| 244 | // by default, use the resolved Data for file lookup | ||
| 245 | string filePathToResolve = (string)objectField.Data; | ||
| 246 | |||
| 247 | // if -bu is used in pyro command, this condition holds true and the tool | ||
| 248 | // will use pre-resolved source for new wixpdb file | ||
| 249 | if (fileResolver.RebaseUpdated) | ||
| 250 | { | ||
| 251 | // try to use the unResolved Source if it exists. | ||
| 252 | // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll | ||
| 253 | // Old version of winpdb file does not contain this attribute and the value is null. | ||
| 254 | if (null != objectField.UnresolvedData) | ||
| 255 | { | ||
| 256 | filePathToResolve = objectField.UnresolvedData; | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | objectField.Data = fileResolver.ResolveFile(filePathToResolve, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Updated); | ||
| 261 | } | ||
| 262 | #endif | ||
| 263 | |||
| 264 | if (!String.Equals(originalFieldPath, resolvedPath, StringComparison.OrdinalIgnoreCase)) | ||
| 265 | { | ||
| 266 | field.Set(resolvedPath); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | catch (WixException e) | ||
| 270 | { | ||
| 271 | this.Messaging.Write(e.Error); | ||
| 272 | } | ||
| 273 | } | ||
| 274 | } | ||
| 275 | } | ||
| 276 | } | ||
diff --git a/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs new file mode 100644 index 00000000..b3b74fbc --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs | |||
| @@ -0,0 +1,196 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Bind | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Core.Native; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class TransferFilesCommand | ||
| 15 | { | ||
| 16 | public TransferFilesCommand(IMessaging messaging, IEnumerable<ILayoutExtension> extensions, IEnumerable<IFileTransfer> fileTransfers, bool resetAcls) | ||
| 17 | { | ||
| 18 | this.Extensions = extensions; | ||
| 19 | this.Messaging = messaging; | ||
| 20 | this.FileTransfers = fileTransfers; | ||
| 21 | this.ResetAcls = resetAcls; | ||
| 22 | } | ||
| 23 | |||
| 24 | private IMessaging Messaging { get; } | ||
| 25 | |||
| 26 | private IEnumerable<ILayoutExtension> Extensions { get; } | ||
| 27 | |||
| 28 | private IEnumerable<IFileTransfer> FileTransfers { get; } | ||
| 29 | |||
| 30 | private bool ResetAcls { get; } | ||
| 31 | |||
| 32 | public void Execute() | ||
| 33 | { | ||
| 34 | var destinationFiles = new List<string>(); | ||
| 35 | |||
| 36 | foreach (var fileTransfer in this.FileTransfers) | ||
| 37 | { | ||
| 38 | // If the source and destination are identical, then there's nothing to do here | ||
| 39 | if (0 == String.Compare(fileTransfer.Source, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) | ||
| 40 | { | ||
| 41 | fileTransfer.Redundant = true; | ||
| 42 | continue; | ||
| 43 | } | ||
| 44 | |||
| 45 | var retry = false; | ||
| 46 | do | ||
| 47 | { | ||
| 48 | try | ||
| 49 | { | ||
| 50 | if (fileTransfer.Move) | ||
| 51 | { | ||
| 52 | this.Messaging.Write(VerboseMessages.MoveFile(fileTransfer.Source, fileTransfer.Destination)); | ||
| 53 | this.MoveFile(fileTransfer.Source, fileTransfer.Destination); | ||
| 54 | } | ||
| 55 | else | ||
| 56 | { | ||
| 57 | this.Messaging.Write(VerboseMessages.CopyFile(fileTransfer.Source, fileTransfer.Destination)); | ||
| 58 | this.CopyFile(fileTransfer.Source, fileTransfer.Destination); | ||
| 59 | } | ||
| 60 | |||
| 61 | retry = false; | ||
| 62 | destinationFiles.Add(fileTransfer.Destination); | ||
| 63 | } | ||
| 64 | catch (FileNotFoundException e) | ||
| 65 | { | ||
| 66 | throw new WixException(ErrorMessages.FileNotFound(fileTransfer.SourceLineNumbers, e.FileName)); | ||
| 67 | } | ||
| 68 | catch (DirectoryNotFoundException) | ||
| 69 | { | ||
| 70 | // if we already retried, give up | ||
| 71 | if (retry) | ||
| 72 | { | ||
| 73 | throw; | ||
| 74 | } | ||
| 75 | |||
| 76 | var directory = Path.GetDirectoryName(fileTransfer.Destination); | ||
| 77 | this.Messaging.Write(VerboseMessages.CreateDirectory(directory)); | ||
| 78 | Directory.CreateDirectory(directory); | ||
| 79 | retry = true; | ||
| 80 | } | ||
| 81 | catch (UnauthorizedAccessException) | ||
| 82 | { | ||
| 83 | // if we already retried, give up | ||
| 84 | if (retry) | ||
| 85 | { | ||
| 86 | throw; | ||
| 87 | } | ||
| 88 | |||
| 89 | if (File.Exists(fileTransfer.Destination)) | ||
| 90 | { | ||
| 91 | this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); | ||
| 92 | |||
| 93 | // try to ensure the file is not read-only | ||
| 94 | var attributes = File.GetAttributes(fileTransfer.Destination); | ||
| 95 | try | ||
| 96 | { | ||
| 97 | File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); | ||
| 98 | } | ||
| 99 | catch (ArgumentException) // thrown for unauthorized access errors | ||
| 100 | { | ||
| 101 | throw new WixException(ErrorMessages.UnauthorizedAccess(fileTransfer.Destination)); | ||
| 102 | } | ||
| 103 | |||
| 104 | // try to delete the file | ||
| 105 | try | ||
| 106 | { | ||
| 107 | File.Delete(fileTransfer.Destination); | ||
| 108 | } | ||
| 109 | catch (IOException) | ||
| 110 | { | ||
| 111 | throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); | ||
| 112 | } | ||
| 113 | |||
| 114 | retry = true; | ||
| 115 | } | ||
| 116 | else // no idea what just happened, bail | ||
| 117 | { | ||
| 118 | throw; | ||
| 119 | } | ||
| 120 | } | ||
| 121 | catch (IOException) | ||
| 122 | { | ||
| 123 | // if we already retried, give up | ||
| 124 | if (retry) | ||
| 125 | { | ||
| 126 | throw; | ||
| 127 | } | ||
| 128 | |||
| 129 | if (File.Exists(fileTransfer.Destination)) | ||
| 130 | { | ||
| 131 | this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); | ||
| 132 | |||
| 133 | // ensure the file is not read-only, then delete it | ||
| 134 | var attributes = File.GetAttributes(fileTransfer.Destination); | ||
| 135 | File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); | ||
| 136 | try | ||
| 137 | { | ||
| 138 | File.Delete(fileTransfer.Destination); | ||
| 139 | } | ||
| 140 | catch (IOException) | ||
| 141 | { | ||
| 142 | throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); | ||
| 143 | } | ||
| 144 | |||
| 145 | retry = true; | ||
| 146 | } | ||
| 147 | else // no idea what just happened, bail | ||
| 148 | { | ||
| 149 | throw; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } while (retry); | ||
| 153 | } | ||
| 154 | |||
| 155 | // Finally, if directed then reset remove ACLs that may may have been picked up | ||
| 156 | // during the file transfer process. | ||
| 157 | if (this.ResetAcls && 0 < destinationFiles.Count) | ||
| 158 | { | ||
| 159 | try | ||
| 160 | { | ||
| 161 | FileSystem.ResetAcls(destinationFiles); | ||
| 162 | } | ||
| 163 | catch (Exception e) | ||
| 164 | { | ||
| 165 | this.Messaging.Write(WarningMessages.UnableToResetAcls(e.Message)); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | private void CopyFile(string source, string destination) | ||
| 171 | { | ||
| 172 | foreach (var extension in this.Extensions) | ||
| 173 | { | ||
| 174 | if (extension.CopyFile(source, destination)) | ||
| 175 | { | ||
| 176 | return; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | FileSystem.CopyFile(source, destination, allowHardlink: true); | ||
| 181 | } | ||
| 182 | |||
| 183 | private void MoveFile(string source, string destination) | ||
| 184 | { | ||
| 185 | foreach (var extension in this.Extensions) | ||
| 186 | { | ||
| 187 | if (extension.MoveFile(source, destination)) | ||
| 188 | { | ||
| 189 | return; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | FileSystem.MoveFile(source, destination); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
diff --git a/src/wix/WixToolset.Core/BindContext.cs b/src/wix/WixToolset.Core/BindContext.cs new file mode 100644 index 00000000..052382f1 --- /dev/null +++ b/src/wix/WixToolset.Core/BindContext.cs | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Threading; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | |||
| 12 | internal class BindContext : IBindContext | ||
| 13 | { | ||
| 14 | internal BindContext(IServiceProvider serviceProvider) | ||
| 15 | { | ||
| 16 | this.ServiceProvider = serviceProvider; | ||
| 17 | } | ||
| 18 | |||
| 19 | public IServiceProvider ServiceProvider { get; } | ||
| 20 | |||
| 21 | public IReadOnlyCollection<BindPath> BindPaths { get; set; } | ||
| 22 | |||
| 23 | public string BurnStubPath { get; set; } | ||
| 24 | |||
| 25 | public int CabbingThreadCount { get; set; } | ||
| 26 | |||
| 27 | public string CabCachePath { get; set; } | ||
| 28 | |||
| 29 | public CompressionLevel? DefaultCompressionLevel { get; set; } | ||
| 30 | |||
| 31 | public IReadOnlyCollection<IDelayedField> DelayedFields { get; set; } | ||
| 32 | |||
| 33 | public IReadOnlyCollection<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; } | ||
| 34 | |||
| 35 | public IReadOnlyCollection<IBinderExtension> Extensions { get; set; } | ||
| 36 | |||
| 37 | public IReadOnlyCollection<IFileSystemExtension> FileSystemExtensions { get; set; } | ||
| 38 | |||
| 39 | public IReadOnlyCollection<string> Ices { get; set; } | ||
| 40 | |||
| 41 | public string IntermediateFolder { get; set; } | ||
| 42 | |||
| 43 | public Intermediate IntermediateRepresentation { get; set; } | ||
| 44 | |||
| 45 | public string OutputPath { get; set; } | ||
| 46 | |||
| 47 | public PdbType PdbType { get; set; } | ||
| 48 | |||
| 49 | public string PdbPath { get; set; } | ||
| 50 | |||
| 51 | public int? ResolvedCodepage { get; set; } | ||
| 52 | |||
| 53 | public int? ResolvedSummaryInformationCodepage { get; set; } | ||
| 54 | |||
| 55 | public int? ResolvedLcid { get; set; } | ||
| 56 | |||
| 57 | public IReadOnlyCollection<string> SuppressIces { get; set; } | ||
| 58 | |||
| 59 | public bool SuppressValidation { get; set; } | ||
| 60 | |||
| 61 | public bool SuppressLayout { get; set; } | ||
| 62 | |||
| 63 | public CancellationToken CancellationToken { get; set; } | ||
| 64 | } | ||
| 65 | } | ||
diff --git a/src/wix/WixToolset.Core/BindFileWithPath.cs b/src/wix/WixToolset.Core/BindFileWithPath.cs new file mode 100644 index 00000000..539600d3 --- /dev/null +++ b/src/wix/WixToolset.Core/BindFileWithPath.cs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Extensibility.Data; | ||
| 6 | |||
| 7 | /// <summary> | ||
| 8 | /// Bind file with its path. | ||
| 9 | /// </summary> | ||
| 10 | internal class BindFileWithPath : IBindFileWithPath | ||
| 11 | { | ||
| 12 | /// <summary> | ||
| 13 | /// Gets or sets the identifier of the file with this path. | ||
| 14 | /// </summary> | ||
| 15 | public string Id { get; set; } | ||
| 16 | |||
| 17 | /// <summary> | ||
| 18 | /// Gets or sets the file path. | ||
| 19 | /// </summary> | ||
| 20 | public string Path { get; set; } | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/wix/WixToolset.Core/BindPath.cs b/src/wix/WixToolset.Core/BindPath.cs new file mode 100644 index 00000000..f70d5e36 --- /dev/null +++ b/src/wix/WixToolset.Core/BindPath.cs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System.Diagnostics; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Bind path representation. | ||
| 10 | /// </summary> | ||
| 11 | [DebuggerDisplay("Name={Name,nq} Path={Path,nq}")] | ||
| 12 | internal class BindPath : IBindPath | ||
| 13 | { | ||
| 14 | public string Name { get; set; } | ||
| 15 | |||
| 16 | public string Path { get; set; } | ||
| 17 | |||
| 18 | public BindStage Stage { get; set; } | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/src/wix/WixToolset.Core/BindResult.cs b/src/wix/WixToolset.Core/BindResult.cs new file mode 100644 index 00000000..9785484c --- /dev/null +++ b/src/wix/WixToolset.Core/BindResult.cs | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Extensibility.Data; | ||
| 9 | |||
| 10 | internal class BindResult : IBindResult | ||
| 11 | { | ||
| 12 | private bool disposed; | ||
| 13 | |||
| 14 | public IReadOnlyCollection<IFileTransfer> FileTransfers { get; set; } | ||
| 15 | |||
| 16 | public IReadOnlyCollection<ITrackedFile> TrackedFiles { get; set; } | ||
| 17 | |||
| 18 | public WixOutput Wixout { get; set; } | ||
| 19 | |||
| 20 | #region IDisposable Support | ||
| 21 | /// <summary> | ||
| 22 | /// Disposes of the internal state of the file structure. | ||
| 23 | /// </summary> | ||
| 24 | public void Dispose() | ||
| 25 | { | ||
| 26 | this.Dispose(true); | ||
| 27 | GC.SuppressFinalize(this); | ||
| 28 | } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Disposes of the internsl state of the file structure. | ||
| 32 | /// </summary> | ||
| 33 | /// <param name="disposing">True if disposing.</param> | ||
| 34 | protected virtual void Dispose(bool disposing) | ||
| 35 | { | ||
| 36 | if (!this.disposed) | ||
| 37 | { | ||
| 38 | if (disposing) | ||
| 39 | { | ||
| 40 | this.Wixout?.Dispose(); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | this.disposed = true; | ||
| 45 | } | ||
| 46 | #endregion | ||
| 47 | } | ||
| 48 | } | ||
diff --git a/src/wix/WixToolset.Core/Binder.cs b/src/wix/WixToolset.Core/Binder.cs new file mode 100644 index 00000000..204ab6ee --- /dev/null +++ b/src/wix/WixToolset.Core/Binder.cs | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.Linq; | ||
| 8 | using System.Reflection; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Extensibility; | ||
| 12 | using WixToolset.Extensibility.Data; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Binder of the WiX toolset. | ||
| 17 | /// </summary> | ||
| 18 | internal class Binder : IBinder | ||
| 19 | { | ||
| 20 | internal Binder(IServiceProvider serviceProvider) | ||
| 21 | { | ||
| 22 | this.ServiceProvider = serviceProvider; | ||
| 23 | } | ||
| 24 | |||
| 25 | public IServiceProvider ServiceProvider { get; } | ||
| 26 | |||
| 27 | public IBindResult Bind(IBindContext context) | ||
| 28 | { | ||
| 29 | // Prebind. | ||
| 30 | // | ||
| 31 | foreach (var extension in context.Extensions) | ||
| 32 | { | ||
| 33 | extension.PreBind(context); | ||
| 34 | } | ||
| 35 | |||
| 36 | // Bind. | ||
| 37 | // | ||
| 38 | this.WriteBuildInfoSymbol(context.IntermediateRepresentation, context.OutputPath, context.PdbPath); | ||
| 39 | |||
| 40 | var bindResult = this.BackendBind(context); | ||
| 41 | |||
| 42 | if (bindResult != null) | ||
| 43 | { | ||
| 44 | // Postbind. | ||
| 45 | // | ||
| 46 | foreach (var extension in context.Extensions) | ||
| 47 | { | ||
| 48 | extension.PostBind(bindResult); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | return bindResult; | ||
| 53 | } | ||
| 54 | |||
| 55 | private IBindResult BackendBind(IBindContext context) | ||
| 56 | { | ||
| 57 | var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); | ||
| 58 | |||
| 59 | var backendFactories = extensionManager.GetServices<IBackendFactory>(); | ||
| 60 | |||
| 61 | var entrySection = context.IntermediateRepresentation.Sections.First(); | ||
| 62 | |||
| 63 | foreach (var factory in backendFactories) | ||
| 64 | { | ||
| 65 | if (factory.TryCreateBackend(entrySection.Type.ToString(), context.OutputPath, out var backend)) | ||
| 66 | { | ||
| 67 | var result = backend.Bind(context); | ||
| 68 | return result; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | // TODO: messaging that a backend could not be found to bind the output type? | ||
| 73 | |||
| 74 | return null; | ||
| 75 | } | ||
| 76 | |||
| 77 | private void WriteBuildInfoSymbol(Intermediate output, string outputFile, string outputPdbPath) | ||
| 78 | { | ||
| 79 | var entrySection = output.Sections.First(s => s.Type != SectionType.Fragment); | ||
| 80 | |||
| 81 | var executingAssembly = Assembly.GetExecutingAssembly(); | ||
| 82 | var fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location); | ||
| 83 | |||
| 84 | var buildInfoSymbol = entrySection.AddSymbol(new WixBuildInfoSymbol() | ||
| 85 | { | ||
| 86 | WixVersion = fileVersion.FileVersion, | ||
| 87 | WixOutputFile = outputFile, | ||
| 88 | }); | ||
| 89 | |||
| 90 | if (!String.IsNullOrEmpty(outputPdbPath)) | ||
| 91 | { | ||
| 92 | buildInfoSymbol.WixPdbFile = outputPdbPath; | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
diff --git a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs new file mode 100644 index 00000000..5f618b81 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs | |||
| @@ -0,0 +1,912 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.CommandLine | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Threading; | ||
| 10 | using System.Threading.Tasks; | ||
| 11 | using System.Xml.Linq; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Extensibility; | ||
| 14 | using WixToolset.Extensibility.Data; | ||
| 15 | using WixToolset.Extensibility.Services; | ||
| 16 | |||
| 17 | internal class BuildCommand : ICommandLineCommand | ||
| 18 | { | ||
| 19 | private readonly CommandLine commandLine; | ||
| 20 | |||
| 21 | public BuildCommand(IServiceProvider serviceProvider) | ||
| 22 | { | ||
| 23 | this.ServiceProvider = serviceProvider; | ||
| 24 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 25 | this.ExtensionManager = serviceProvider.GetService<IExtensionManager>(); | ||
| 26 | this.commandLine = new CommandLine(this.ServiceProvider, this.Messaging); | ||
| 27 | } | ||
| 28 | |||
| 29 | public bool ShowLogo => this.commandLine.ShowLogo; | ||
| 30 | |||
| 31 | public bool StopParsing => this.commandLine.ShowHelp; | ||
| 32 | |||
| 33 | private IServiceProvider ServiceProvider { get; } | ||
| 34 | |||
| 35 | private IMessaging Messaging { get; } | ||
| 36 | |||
| 37 | private IExtensionManager ExtensionManager { get; } | ||
| 38 | |||
| 39 | private string IntermediateFolder { get; set; } | ||
| 40 | |||
| 41 | private OutputType OutputType { get; set; } | ||
| 42 | |||
| 43 | private List<string> IncludeSearchPaths { get; set; } | ||
| 44 | |||
| 45 | public string PdbFile { get; set; } | ||
| 46 | |||
| 47 | public PdbType PdbType { get; set; } | ||
| 48 | |||
| 49 | private Platform Platform { get; set; } | ||
| 50 | |||
| 51 | private string OutputFile { get; set; } | ||
| 52 | |||
| 53 | private CompressionLevel? DefaultCompressionLevel { get; set; } | ||
| 54 | |||
| 55 | private string ContentsFile { get; set; } | ||
| 56 | |||
| 57 | private string OutputsFile { get; set; } | ||
| 58 | |||
| 59 | private string BuiltOutputsFile { get; set; } | ||
| 60 | |||
| 61 | public Task<int> ExecuteAsync(CancellationToken cancellationToken) | ||
| 62 | { | ||
| 63 | if (this.commandLine.ShowHelp) | ||
| 64 | { | ||
| 65 | Console.WriteLine("TODO: Show build command help"); | ||
| 66 | return Task.FromResult(-1); | ||
| 67 | } | ||
| 68 | |||
| 69 | this.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); | ||
| 70 | |||
| 71 | this.OutputType = this.commandLine.CalculateOutputType(); | ||
| 72 | |||
| 73 | this.IncludeSearchPaths = this.commandLine.IncludeSearchPaths; | ||
| 74 | |||
| 75 | this.PdbFile = this.commandLine.PdbFile; | ||
| 76 | |||
| 77 | this.PdbType = this.commandLine.PdbType; | ||
| 78 | |||
| 79 | this.Platform = this.commandLine.Platform; | ||
| 80 | |||
| 81 | this.ContentsFile = this.commandLine.ContentsFile; | ||
| 82 | |||
| 83 | this.OutputsFile = this.commandLine.OutputsFile; | ||
| 84 | |||
| 85 | this.BuiltOutputsFile = this.commandLine.BuiltOutputsFile; | ||
| 86 | |||
| 87 | this.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel; | ||
| 88 | |||
| 89 | var preprocessorVariables = this.commandLine.GatherPreprocessorVariables(); | ||
| 90 | |||
| 91 | var sourceFiles = this.commandLine.GatherSourceFiles(this.IntermediateFolder); | ||
| 92 | |||
| 93 | var filterCultures = this.commandLine.CalculateFilterCultures(); | ||
| 94 | |||
| 95 | var creator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>(); | ||
| 96 | |||
| 97 | this.EvaluateSourceFiles(sourceFiles, creator, out var codeFiles, out var wixipl); | ||
| 98 | |||
| 99 | this.OutputFile = this.commandLine.OutputFile; | ||
| 100 | |||
| 101 | if (String.IsNullOrEmpty(this.OutputFile)) | ||
| 102 | { | ||
| 103 | if (codeFiles.Count == 1) | ||
| 104 | { | ||
| 105 | // If output type is unknown, the extension will be replaced with the right default based on output type. | ||
| 106 | this.OutputFile = Path.ChangeExtension(codeFiles[0].OutputPath, DefaultExtensionForOutputType(this.OutputType)); | ||
| 107 | } | ||
| 108 | else | ||
| 109 | { | ||
| 110 | this.Messaging.Write(ErrorMessages.MustSpecifyOutputWithMoreThanOneInput()); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | if (this.Messaging.EncounteredError) | ||
| 115 | { | ||
| 116 | return Task.FromResult(this.Messaging.LastErrorNumber); | ||
| 117 | } | ||
| 118 | |||
| 119 | var wixobjs = this.CompilePhase(preprocessorVariables, codeFiles, cancellationToken); | ||
| 120 | |||
| 121 | var wxls = this.LoadLocalizationFiles(this.commandLine.LocalizationFilePaths, preprocessorVariables, cancellationToken); | ||
| 122 | |||
| 123 | if (this.Messaging.EncounteredError) | ||
| 124 | { | ||
| 125 | return Task.FromResult(this.Messaging.LastErrorNumber); | ||
| 126 | } | ||
| 127 | |||
| 128 | if (this.OutputType == OutputType.Library) | ||
| 129 | { | ||
| 130 | using (new IntermediateFieldContext("wix.lib")) | ||
| 131 | { | ||
| 132 | var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths, cancellationToken); | ||
| 133 | |||
| 134 | if (!this.Messaging.EncounteredError) | ||
| 135 | { | ||
| 136 | wixlib.Save(this.OutputFile); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | } | ||
| 140 | else | ||
| 141 | { | ||
| 142 | using (new IntermediateFieldContext("wix.link")) | ||
| 143 | { | ||
| 144 | if (wixipl == null) | ||
| 145 | { | ||
| 146 | wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator, cancellationToken); | ||
| 147 | } | ||
| 148 | |||
| 149 | if (!this.Messaging.EncounteredError) | ||
| 150 | { | ||
| 151 | var outputExtension = Path.GetExtension(this.OutputFile); | ||
| 152 | if (String.IsNullOrEmpty(outputExtension) || ".wix" == outputExtension) | ||
| 153 | { | ||
| 154 | var entrySectionType = wixipl.Sections.Single().Type; | ||
| 155 | this.OutputFile = Path.ChangeExtension(this.OutputFile, DefaultExtensionForSectionType(entrySectionType)); | ||
| 156 | } | ||
| 157 | |||
| 158 | if (this.OutputType == OutputType.IntermediatePostLink) | ||
| 159 | { | ||
| 160 | wixipl.Save(this.OutputFile); | ||
| 161 | } | ||
| 162 | else | ||
| 163 | { | ||
| 164 | using (new IntermediateFieldContext("wix.bind")) | ||
| 165 | { | ||
| 166 | this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, cancellationToken); | ||
| 167 | } | ||
| 168 | } | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | return Task.FromResult(this.Messaging.LastErrorNumber); | ||
| 174 | } | ||
| 175 | |||
| 176 | public bool TryParseArgument(ICommandLineParser parser, string argument) | ||
| 177 | { | ||
| 178 | return this.commandLine.TryParseArgument(argument, parser); | ||
| 179 | } | ||
| 180 | |||
| 181 | private void EvaluateSourceFiles(IEnumerable<SourceFile> sourceFiles, ISymbolDefinitionCreator creator, out List<SourceFile> codeFiles, out Intermediate wixipl) | ||
| 182 | { | ||
| 183 | codeFiles = new List<SourceFile>(); | ||
| 184 | |||
| 185 | wixipl = null; | ||
| 186 | |||
| 187 | foreach (var sourceFile in sourceFiles) | ||
| 188 | { | ||
| 189 | var extension = Path.GetExtension(sourceFile.SourcePath); | ||
| 190 | |||
| 191 | if (wixipl != null || ".wxs".Equals(extension, StringComparison.OrdinalIgnoreCase)) | ||
| 192 | { | ||
| 193 | codeFiles.Add(sourceFile); | ||
| 194 | } | ||
| 195 | else | ||
| 196 | { | ||
| 197 | try | ||
| 198 | { | ||
| 199 | wixipl = Intermediate.Load(sourceFile.SourcePath, creator); | ||
| 200 | } | ||
| 201 | catch (WixException) | ||
| 202 | { | ||
| 203 | // We'll assume anything that isn't a valid intermediate is source code to compile. | ||
| 204 | codeFiles.Add(sourceFile); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | if (wixipl == null && codeFiles.Count == 0) | ||
| 210 | { | ||
| 211 | this.Messaging.Write(ErrorMessages.NoSourceFiles()); | ||
| 212 | } | ||
| 213 | else if (wixipl != null && codeFiles.Count != 0) | ||
| 214 | { | ||
| 215 | this.Messaging.Write(ErrorMessages.WixiplSourceFileIsExclusive()); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | private IReadOnlyList<Intermediate> CompilePhase(IDictionary<string, string> preprocessorVariables, IEnumerable<SourceFile> sourceFiles, CancellationToken cancellationToken) | ||
| 220 | { | ||
| 221 | var intermediates = new List<Intermediate>(); | ||
| 222 | |||
| 223 | foreach (var sourceFile in sourceFiles) | ||
| 224 | { | ||
| 225 | var document = this.Preprocess(preprocessorVariables, sourceFile.SourcePath, cancellationToken); | ||
| 226 | |||
| 227 | if (this.Messaging.EncounteredError) | ||
| 228 | { | ||
| 229 | continue; | ||
| 230 | } | ||
| 231 | |||
| 232 | var context = this.ServiceProvider.GetService<ICompileContext>(); | ||
| 233 | context.Extensions = this.ExtensionManager.GetServices<ICompilerExtension>(); | ||
| 234 | context.Platform = this.Platform; | ||
| 235 | context.Source = document; | ||
| 236 | context.CancellationToken = cancellationToken; | ||
| 237 | |||
| 238 | Intermediate intermediate = null; | ||
| 239 | try | ||
| 240 | { | ||
| 241 | var compiler = this.ServiceProvider.GetService<ICompiler>(); | ||
| 242 | intermediate = compiler.Compile(context); | ||
| 243 | } | ||
| 244 | catch (WixException e) | ||
| 245 | { | ||
| 246 | this.Messaging.Write(e.Error); | ||
| 247 | } | ||
| 248 | |||
| 249 | if (this.Messaging.EncounteredError) | ||
| 250 | { | ||
| 251 | continue; | ||
| 252 | } | ||
| 253 | |||
| 254 | intermediates.Add(intermediate); | ||
| 255 | } | ||
| 256 | |||
| 257 | return intermediates; | ||
| 258 | } | ||
| 259 | |||
| 260 | private Intermediate LibraryPhase(IReadOnlyCollection<Intermediate> intermediates, IReadOnlyCollection<Localization> localizations, bool bindFiles, IReadOnlyCollection<IBindPath> bindPaths, CancellationToken cancellationToken) | ||
| 261 | { | ||
| 262 | var context = this.ServiceProvider.GetService<ILibraryContext>(); | ||
| 263 | context.BindFiles = bindFiles; | ||
| 264 | context.BindPaths = bindPaths; | ||
| 265 | context.Extensions = this.ExtensionManager.GetServices<ILibrarianExtension>(); | ||
| 266 | context.Localizations = localizations; | ||
| 267 | context.Intermediates = intermediates; | ||
| 268 | context.CancellationToken = cancellationToken; | ||
| 269 | |||
| 270 | Intermediate library = null; | ||
| 271 | try | ||
| 272 | { | ||
| 273 | var librarian = this.ServiceProvider.GetService<ILibrarian>(); | ||
| 274 | library = librarian.Combine(context); | ||
| 275 | } | ||
| 276 | catch (WixException e) | ||
| 277 | { | ||
| 278 | this.Messaging.Write(e.Error); | ||
| 279 | } | ||
| 280 | |||
| 281 | return library; | ||
| 282 | } | ||
| 283 | |||
| 284 | private Intermediate LinkPhase(IEnumerable<Intermediate> intermediates, IEnumerable<string> libraryFiles, ISymbolDefinitionCreator creator, CancellationToken cancellationToken) | ||
| 285 | { | ||
| 286 | var libraries = this.LoadLibraries(libraryFiles, creator); | ||
| 287 | |||
| 288 | if (this.Messaging.EncounteredError) | ||
| 289 | { | ||
| 290 | return null; | ||
| 291 | } | ||
| 292 | |||
| 293 | var context = this.ServiceProvider.GetService<ILinkContext>(); | ||
| 294 | context.Extensions = this.ExtensionManager.GetServices<ILinkerExtension>(); | ||
| 295 | context.ExtensionData = this.ExtensionManager.GetServices<IExtensionData>(); | ||
| 296 | context.ExpectedOutputType = this.OutputType; | ||
| 297 | context.Intermediates = intermediates.Concat(libraries).ToList(); | ||
| 298 | context.SymbolDefinitionCreator = creator; | ||
| 299 | context.CancellationToken = cancellationToken; | ||
| 300 | |||
| 301 | var linker = this.ServiceProvider.GetService<ILinker>(); | ||
| 302 | return linker.Link(context); | ||
| 303 | } | ||
| 304 | |||
| 305 | private void BindPhase(Intermediate output, IReadOnlyCollection<Localization> localizations, IReadOnlyCollection<string> filterCultures, string cabCachePath, IReadOnlyCollection<IBindPath> bindPaths, CancellationToken cancellationToken) | ||
| 306 | { | ||
| 307 | var intermediateFolder = this.IntermediateFolder; | ||
| 308 | if (String.IsNullOrEmpty(intermediateFolder)) | ||
| 309 | { | ||
| 310 | intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | ||
| 311 | } | ||
| 312 | |||
| 313 | IResolveResult resolveResult; | ||
| 314 | { | ||
| 315 | var context = this.ServiceProvider.GetService<IResolveContext>(); | ||
| 316 | context.BindPaths = bindPaths; | ||
| 317 | context.Extensions = this.ExtensionManager.GetServices<IResolverExtension>(); | ||
| 318 | context.ExtensionData = this.ExtensionManager.GetServices<IExtensionData>(); | ||
| 319 | context.FilterCultures = filterCultures; | ||
| 320 | context.IntermediateFolder = intermediateFolder; | ||
| 321 | context.IntermediateRepresentation = output; | ||
| 322 | context.Localizations = localizations; | ||
| 323 | context.CancellationToken = cancellationToken; | ||
| 324 | |||
| 325 | var resolver = this.ServiceProvider.GetService<IResolver>(); | ||
| 326 | resolveResult = resolver.Resolve(context); | ||
| 327 | } | ||
| 328 | |||
| 329 | if (this.Messaging.EncounteredError) | ||
| 330 | { | ||
| 331 | return; | ||
| 332 | } | ||
| 333 | |||
| 334 | IBindResult bindResult = null; | ||
| 335 | try | ||
| 336 | { | ||
| 337 | { | ||
| 338 | var context = this.ServiceProvider.GetService<IBindContext>(); | ||
| 339 | //context.CabbingThreadCount = this.CabbingThreadCount; | ||
| 340 | context.CabCachePath = cabCachePath; | ||
| 341 | context.ResolvedCodepage = resolveResult.Codepage; | ||
| 342 | context.ResolvedSummaryInformationCodepage = resolveResult.SummaryInformationCodepage; | ||
| 343 | context.ResolvedLcid = resolveResult.PackageLcid; | ||
| 344 | context.DefaultCompressionLevel = this.DefaultCompressionLevel; | ||
| 345 | context.DelayedFields = resolveResult.DelayedFields; | ||
| 346 | context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; | ||
| 347 | context.Extensions = this.ExtensionManager.GetServices<IBinderExtension>(); | ||
| 348 | context.FileSystemExtensions = this.ExtensionManager.GetServices<IFileSystemExtension>(); | ||
| 349 | context.Ices = this.commandLine.Ices; | ||
| 350 | context.IntermediateFolder = intermediateFolder; | ||
| 351 | context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; | ||
| 352 | context.OutputPath = this.OutputFile; | ||
| 353 | context.PdbType = this.PdbType; | ||
| 354 | context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb"); | ||
| 355 | context.SuppressIces = this.commandLine.SuppressIces; | ||
| 356 | context.SuppressValidation = this.commandLine.SuppressValidation; | ||
| 357 | context.CancellationToken = cancellationToken; | ||
| 358 | |||
| 359 | var binder = this.ServiceProvider.GetService<IBinder>(); | ||
| 360 | bindResult = binder.Bind(context); | ||
| 361 | } | ||
| 362 | |||
| 363 | if (this.Messaging.EncounteredError) | ||
| 364 | { | ||
| 365 | return; | ||
| 366 | } | ||
| 367 | |||
| 368 | { | ||
| 369 | var context = this.ServiceProvider.GetService<ILayoutContext>(); | ||
| 370 | context.Extensions = this.ExtensionManager.GetServices<ILayoutExtension>(); | ||
| 371 | context.TrackedFiles = bindResult.TrackedFiles; | ||
| 372 | context.FileTransfers = bindResult.FileTransfers; | ||
| 373 | context.IntermediateFolder = intermediateFolder; | ||
| 374 | context.ContentsFile = this.ContentsFile; | ||
| 375 | context.OutputsFile = this.OutputsFile; | ||
| 376 | context.BuiltOutputsFile = this.BuiltOutputsFile; | ||
| 377 | context.ResetAcls = this.commandLine.ResetAcls; | ||
| 378 | context.CancellationToken = cancellationToken; | ||
| 379 | |||
| 380 | var layout = this.ServiceProvider.GetService<ILayoutCreator>(); | ||
| 381 | layout.Layout(context); | ||
| 382 | } | ||
| 383 | } | ||
| 384 | finally | ||
| 385 | { | ||
| 386 | bindResult?.Dispose(); | ||
| 387 | } | ||
| 388 | } | ||
| 389 | |||
| 390 | private IEnumerable<Intermediate> LoadLibraries(IEnumerable<string> libraryFiles, ISymbolDefinitionCreator creator) | ||
| 391 | { | ||
| 392 | try | ||
| 393 | { | ||
| 394 | return Intermediate.Load(libraryFiles, creator); | ||
| 395 | } | ||
| 396 | catch (WixCorruptFileException e) | ||
| 397 | { | ||
| 398 | this.Messaging.Write(e.Error); | ||
| 399 | } | ||
| 400 | catch (WixUnexpectedFileFormatException e) | ||
| 401 | { | ||
| 402 | this.Messaging.Write(e.Error); | ||
| 403 | } | ||
| 404 | |||
| 405 | return Array.Empty<Intermediate>(); | ||
| 406 | } | ||
| 407 | |||
| 408 | private IReadOnlyList<Localization> LoadLocalizationFiles(IEnumerable<string> locFiles, IDictionary<string, string> preprocessorVariables, CancellationToken cancellationToken) | ||
| 409 | { | ||
| 410 | var localizations = new List<Localization>(); | ||
| 411 | var parser = this.ServiceProvider.GetService<ILocalizationParser>(); | ||
| 412 | |||
| 413 | foreach (var loc in locFiles) | ||
| 414 | { | ||
| 415 | var document = this.Preprocess(preprocessorVariables, loc, cancellationToken); | ||
| 416 | |||
| 417 | if (this.Messaging.EncounteredError) | ||
| 418 | { | ||
| 419 | continue; | ||
| 420 | } | ||
| 421 | |||
| 422 | var localization = parser.ParseLocalization(document); | ||
| 423 | localizations.Add(localization); | ||
| 424 | } | ||
| 425 | |||
| 426 | return localizations; | ||
| 427 | } | ||
| 428 | |||
| 429 | private XDocument Preprocess(IDictionary<string, string> preprocessorVariables, string sourcePath, CancellationToken cancellationToken) | ||
| 430 | { | ||
| 431 | var context = this.ServiceProvider.GetService<IPreprocessContext>(); | ||
| 432 | context.Extensions = this.ExtensionManager.GetServices<IPreprocessorExtension>(); | ||
| 433 | context.Platform = this.Platform; | ||
| 434 | context.IncludeSearchPaths = this.IncludeSearchPaths; | ||
| 435 | context.SourcePath = sourcePath; | ||
| 436 | context.Variables = preprocessorVariables; | ||
| 437 | context.CancellationToken = cancellationToken; | ||
| 438 | |||
| 439 | IPreprocessResult result = null; | ||
| 440 | try | ||
| 441 | { | ||
| 442 | var preprocessor = this.ServiceProvider.GetService<IPreprocessor>(); | ||
| 443 | result = preprocessor.Preprocess(context); | ||
| 444 | } | ||
| 445 | catch (WixException e) | ||
| 446 | { | ||
| 447 | this.Messaging.Write(e.Error); | ||
| 448 | } | ||
| 449 | |||
| 450 | return result?.Document; | ||
| 451 | } | ||
| 452 | |||
| 453 | private static string DefaultExtensionForSectionType(SectionType sectionType) | ||
| 454 | { | ||
| 455 | switch (sectionType) | ||
| 456 | { | ||
| 457 | case SectionType.Bundle: | ||
| 458 | return ".exe"; | ||
| 459 | case SectionType.Module: | ||
| 460 | return ".msm"; | ||
| 461 | case SectionType.Product: | ||
| 462 | return ".msi"; | ||
| 463 | case SectionType.PatchCreation: | ||
| 464 | return ".pcp"; | ||
| 465 | case SectionType.Patch: | ||
| 466 | return ".msp"; | ||
| 467 | case SectionType.Fragment: | ||
| 468 | case SectionType.Unknown: | ||
| 469 | default: | ||
| 470 | return ".wix"; | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | private static string DefaultExtensionForOutputType(OutputType outputType) | ||
| 475 | { | ||
| 476 | switch (outputType) | ||
| 477 | { | ||
| 478 | case OutputType.Bundle: | ||
| 479 | return ".exe"; | ||
| 480 | case OutputType.Library: | ||
| 481 | return ".wixlib"; | ||
| 482 | case OutputType.Module: | ||
| 483 | return ".msm"; | ||
| 484 | case OutputType.Patch: | ||
| 485 | return ".msp"; | ||
| 486 | case OutputType.PatchCreation: | ||
| 487 | return ".pcp"; | ||
| 488 | case OutputType.Product: | ||
| 489 | return ".msi"; | ||
| 490 | case OutputType.Transform: | ||
| 491 | return ".mst"; | ||
| 492 | case OutputType.IntermediatePostLink: | ||
| 493 | return ".wixipl"; | ||
| 494 | case OutputType.Unknown: | ||
| 495 | default: | ||
| 496 | return ".wix"; | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | private class CommandLine | ||
| 501 | { | ||
| 502 | private static readonly char[] BindPathSplit = { '=' }; | ||
| 503 | |||
| 504 | public bool BindFiles { get; private set; } | ||
| 505 | |||
| 506 | public List<IBindPath> BindPaths { get; } = new List<IBindPath>(); | ||
| 507 | |||
| 508 | public string CabCachePath { get; private set; } | ||
| 509 | |||
| 510 | public List<string> Cultures { get; } = new List<string>(); | ||
| 511 | |||
| 512 | public List<string> Defines { get; } = new List<string>(); | ||
| 513 | |||
| 514 | public List<string> IncludeSearchPaths { get; } = new List<string>(); | ||
| 515 | |||
| 516 | public List<string> LocalizationFilePaths { get; } = new List<string>(); | ||
| 517 | |||
| 518 | public List<string> LibraryFilePaths { get; } = new List<string>(); | ||
| 519 | |||
| 520 | public List<string> SourceFilePaths { get; } = new List<string>(); | ||
| 521 | |||
| 522 | public Platform Platform { get; private set; } | ||
| 523 | |||
| 524 | public string PdbFile { get; private set; } | ||
| 525 | |||
| 526 | public PdbType PdbType { get; private set; } | ||
| 527 | |||
| 528 | public bool ShowLogo { get; private set; } | ||
| 529 | |||
| 530 | public bool ShowHelp { get; private set; } | ||
| 531 | |||
| 532 | public string IntermediateFolder { get; private set; } | ||
| 533 | |||
| 534 | public string OutputFile { get; private set; } | ||
| 535 | |||
| 536 | public string OutputType { get; private set; } | ||
| 537 | |||
| 538 | public CompressionLevel? DefaultCompressionLevel { get; private set; } | ||
| 539 | |||
| 540 | public string ContentsFile { get; private set; } | ||
| 541 | |||
| 542 | public string OutputsFile { get; private set; } | ||
| 543 | |||
| 544 | public string BuiltOutputsFile { get; private set; } | ||
| 545 | |||
| 546 | public List<string> Ices { get; } = new List<string>(); | ||
| 547 | |||
| 548 | public List<string> SuppressIces { get; } = new List<string>(); | ||
| 549 | |||
| 550 | public bool SuppressValidation { get; set; } | ||
| 551 | |||
| 552 | public bool ResetAcls { get; set; } | ||
| 553 | |||
| 554 | public CommandLine(IServiceProvider serviceProvider, IMessaging messaging) | ||
| 555 | { | ||
| 556 | this.ServiceProvider = serviceProvider; | ||
| 557 | this.Messaging = messaging; | ||
| 558 | } | ||
| 559 | |||
| 560 | private IServiceProvider ServiceProvider { get; } | ||
| 561 | |||
| 562 | private IMessaging Messaging { get; } | ||
| 563 | |||
| 564 | public bool TryParseArgument(string arg, ICommandLineParser parser) | ||
| 565 | { | ||
| 566 | if (parser.IsSwitch(arg)) | ||
| 567 | { | ||
| 568 | var parameter = arg.Substring(1).ToLowerInvariant(); | ||
| 569 | switch (parameter) | ||
| 570 | { | ||
| 571 | case "?": | ||
| 572 | case "h": | ||
| 573 | case "help": | ||
| 574 | this.ShowHelp = true; | ||
| 575 | return true; | ||
| 576 | |||
| 577 | case "arch": | ||
| 578 | case "platform": | ||
| 579 | { | ||
| 580 | var value = parser.GetNextArgumentOrError(arg); | ||
| 581 | if (Enum.TryParse(value, true, out Platform platform)) | ||
| 582 | { | ||
| 583 | this.Platform = platform; | ||
| 584 | return true; | ||
| 585 | } | ||
| 586 | break; | ||
| 587 | } | ||
| 588 | |||
| 589 | case "bf": | ||
| 590 | case "bindfiles": | ||
| 591 | this.BindFiles = true; | ||
| 592 | return true; | ||
| 593 | |||
| 594 | case "bindpath": | ||
| 595 | { | ||
| 596 | var value = parser.GetNextArgumentOrError(arg); | ||
| 597 | if (value != null && this.TryParseBindPath(value, out var bindPath)) | ||
| 598 | { | ||
| 599 | this.BindPaths.Add(bindPath); | ||
| 600 | return true; | ||
| 601 | } | ||
| 602 | return false; | ||
| 603 | } | ||
| 604 | |||
| 605 | case "cc": | ||
| 606 | this.CabCachePath = parser.GetNextArgumentOrError(arg); | ||
| 607 | return true; | ||
| 608 | |||
| 609 | case "culture": | ||
| 610 | parser.GetNextArgumentOrError(arg, this.Cultures); | ||
| 611 | return true; | ||
| 612 | |||
| 613 | case "contentsfile": | ||
| 614 | this.ContentsFile = parser.GetNextArgumentAsFilePathOrError(arg); | ||
| 615 | return true; | ||
| 616 | |||
| 617 | case "outputsfile": | ||
| 618 | this.OutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); | ||
| 619 | return true; | ||
| 620 | |||
| 621 | case "builtoutputsfile": | ||
| 622 | this.BuiltOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); | ||
| 623 | return true; | ||
| 624 | |||
| 625 | case "d": | ||
| 626 | case "define": | ||
| 627 | parser.GetNextArgumentOrError(arg, this.Defines); | ||
| 628 | return true; | ||
| 629 | |||
| 630 | case "dcl": | ||
| 631 | case "defaultcompressionlevel": | ||
| 632 | { | ||
| 633 | var value = parser.GetNextArgumentOrError(arg); | ||
| 634 | if (Enum.TryParse(value, true, out CompressionLevel compressionLevel)) | ||
| 635 | { | ||
| 636 | this.DefaultCompressionLevel = compressionLevel; | ||
| 637 | return true; | ||
| 638 | } | ||
| 639 | return false; | ||
| 640 | } | ||
| 641 | |||
| 642 | case "i": | ||
| 643 | case "includepath": | ||
| 644 | parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths); | ||
| 645 | return true; | ||
| 646 | |||
| 647 | case "ice": | ||
| 648 | { | ||
| 649 | var value = parser.GetNextArgumentOrError(arg); | ||
| 650 | this.Ices.Add(value); | ||
| 651 | return true; | ||
| 652 | } | ||
| 653 | |||
| 654 | case "intermediatefolder": | ||
| 655 | this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); | ||
| 656 | return true; | ||
| 657 | |||
| 658 | case "loc": | ||
| 659 | parser.GetNextArgumentAsFilePathOrError(arg, "localization files", this.LocalizationFilePaths); | ||
| 660 | return true; | ||
| 661 | |||
| 662 | case "lib": | ||
| 663 | parser.GetNextArgumentAsFilePathOrError(arg, "library files", this.LibraryFilePaths); | ||
| 664 | return true; | ||
| 665 | |||
| 666 | case "o": | ||
| 667 | case "out": | ||
| 668 | this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); | ||
| 669 | return true; | ||
| 670 | |||
| 671 | case "outputtype": | ||
| 672 | this.OutputType = parser.GetNextArgumentOrError(arg); | ||
| 673 | return true; | ||
| 674 | |||
| 675 | case "pdb": | ||
| 676 | this.PdbFile = parser.GetNextArgumentAsFilePathOrError(arg); | ||
| 677 | return true; | ||
| 678 | |||
| 679 | case "pdbtype": | ||
| 680 | { | ||
| 681 | var value = parser.GetNextArgumentOrError(arg); | ||
| 682 | if (Enum.TryParse(value, true, out PdbType pdbType)) | ||
| 683 | { | ||
| 684 | this.PdbType = pdbType; | ||
| 685 | return true; | ||
| 686 | } | ||
| 687 | return false; | ||
| 688 | } | ||
| 689 | |||
| 690 | case "sice": | ||
| 691 | { | ||
| 692 | var value = parser.GetNextArgumentOrError(arg); | ||
| 693 | this.SuppressIces.Add(value); | ||
| 694 | return true; | ||
| 695 | } | ||
| 696 | |||
| 697 | case "nologo": | ||
| 698 | this.ShowLogo = false; | ||
| 699 | return true; | ||
| 700 | |||
| 701 | case "v": | ||
| 702 | case "verbose": | ||
| 703 | this.Messaging.ShowVerboseMessages = true; | ||
| 704 | return true; | ||
| 705 | |||
| 706 | case "sval": | ||
| 707 | this.SuppressValidation = true; | ||
| 708 | return true; | ||
| 709 | |||
| 710 | case "resetacls": | ||
| 711 | this.ResetAcls = true; | ||
| 712 | return true; | ||
| 713 | } | ||
| 714 | |||
| 715 | if (parameter.StartsWith("sw")) | ||
| 716 | { | ||
| 717 | this.ParseSuppressWarning(parameter, "sw".Length, parser); | ||
| 718 | return true; | ||
| 719 | } | ||
| 720 | else if (parameter.StartsWith("suppresswarning")) | ||
| 721 | { | ||
| 722 | this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); | ||
| 723 | return true; | ||
| 724 | } | ||
| 725 | else if (parameter.StartsWith("wx")) | ||
| 726 | { | ||
| 727 | this.ParseWarningAsError(parameter, "wx".Length, parser); | ||
| 728 | return true; | ||
| 729 | } | ||
| 730 | |||
| 731 | return false; | ||
| 732 | } | ||
| 733 | else | ||
| 734 | { | ||
| 735 | parser.GetArgumentAsFilePathOrError(arg, "source code", this.SourceFilePaths); | ||
| 736 | return true; | ||
| 737 | } | ||
| 738 | } | ||
| 739 | |||
| 740 | public string CalculateIntermedateFolder() | ||
| 741 | { | ||
| 742 | return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; | ||
| 743 | } | ||
| 744 | |||
| 745 | public OutputType CalculateOutputType() | ||
| 746 | { | ||
| 747 | if (String.IsNullOrEmpty(this.OutputType)) | ||
| 748 | { | ||
| 749 | this.OutputType = Path.GetExtension(this.OutputFile); | ||
| 750 | } | ||
| 751 | |||
| 752 | switch (this.OutputType?.ToLowerInvariant()) | ||
| 753 | { | ||
| 754 | case "bundle": | ||
| 755 | case ".exe": | ||
| 756 | return Data.OutputType.Bundle; | ||
| 757 | |||
| 758 | case "library": | ||
| 759 | case ".wixlib": | ||
| 760 | return Data.OutputType.Library; | ||
| 761 | |||
| 762 | case "module": | ||
| 763 | case ".msm": | ||
| 764 | return Data.OutputType.Module; | ||
| 765 | |||
| 766 | case "patch": | ||
| 767 | case ".msp": | ||
| 768 | return Data.OutputType.Patch; | ||
| 769 | |||
| 770 | case ".pcp": | ||
| 771 | return Data.OutputType.PatchCreation; | ||
| 772 | |||
| 773 | case "product": | ||
| 774 | case "package": | ||
| 775 | case ".msi": | ||
| 776 | return Data.OutputType.Product; | ||
| 777 | |||
| 778 | case "transform": | ||
| 779 | case ".mst": | ||
| 780 | return Data.OutputType.Transform; | ||
| 781 | |||
| 782 | case "intermediatepostlink": | ||
| 783 | case ".wixipl": | ||
| 784 | return Data.OutputType.IntermediatePostLink; | ||
| 785 | } | ||
| 786 | |||
| 787 | return Data.OutputType.Unknown; | ||
| 788 | } | ||
| 789 | |||
| 790 | public IReadOnlyList<string> CalculateFilterCultures() | ||
| 791 | { | ||
| 792 | var result = new List<string>(); | ||
| 793 | |||
| 794 | if (this.Cultures == null) | ||
| 795 | { | ||
| 796 | } | ||
| 797 | else if (this.Cultures.Count == 1 && this.Cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase)) | ||
| 798 | { | ||
| 799 | // When null is used treat it as if cultures wasn't specified. This is | ||
| 800 | // needed for batching in the MSBuild task since MSBuild doesn't support | ||
| 801 | // empty items. | ||
| 802 | } | ||
| 803 | else | ||
| 804 | { | ||
| 805 | foreach (var culture in this.Cultures) | ||
| 806 | { | ||
| 807 | // Neutral is different from null. For neutral we still want to do culture filtering. | ||
| 808 | // Set the culture to the empty string = identifier for the invariant culture. | ||
| 809 | var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture; | ||
| 810 | result.Add(filter); | ||
| 811 | } | ||
| 812 | } | ||
| 813 | |||
| 814 | return result; | ||
| 815 | } | ||
| 816 | |||
| 817 | public IDictionary<string, string> GatherPreprocessorVariables() | ||
| 818 | { | ||
| 819 | var variables = new Dictionary<string, string>(); | ||
| 820 | |||
| 821 | foreach (var pair in this.Defines) | ||
| 822 | { | ||
| 823 | var value = pair.Split(new[] { '=' }, 2); | ||
| 824 | |||
| 825 | if (variables.ContainsKey(value[0])) | ||
| 826 | { | ||
| 827 | this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]])); | ||
| 828 | continue; | ||
| 829 | } | ||
| 830 | |||
| 831 | variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]); | ||
| 832 | } | ||
| 833 | |||
| 834 | return variables; | ||
| 835 | } | ||
| 836 | |||
| 837 | public IEnumerable<SourceFile> GatherSourceFiles(string intermediateDirectory) | ||
| 838 | { | ||
| 839 | var files = new List<SourceFile>(); | ||
| 840 | |||
| 841 | foreach (var item in this.SourceFilePaths) | ||
| 842 | { | ||
| 843 | var sourcePath = item; | ||
| 844 | var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir"); | ||
| 845 | |||
| 846 | files.Add(new SourceFile(sourcePath, outputPath)); | ||
| 847 | } | ||
| 848 | |||
| 849 | return files; | ||
| 850 | } | ||
| 851 | |||
| 852 | private bool TryParseBindPath(string bindPath, out IBindPath bp) | ||
| 853 | { | ||
| 854 | var namedPath = bindPath.Split(BindPathSplit, 2); | ||
| 855 | |||
| 856 | bp = this.ServiceProvider.GetService<IBindPath>(); | ||
| 857 | |||
| 858 | if (1 == namedPath.Length) | ||
| 859 | { | ||
| 860 | bp.Path = namedPath[0]; | ||
| 861 | } | ||
| 862 | else | ||
| 863 | { | ||
| 864 | bp.Name = namedPath[0]; | ||
| 865 | bp.Path = namedPath[1]; | ||
| 866 | } | ||
| 867 | |||
| 868 | if (File.Exists(bp.Path)) | ||
| 869 | { | ||
| 870 | this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); | ||
| 871 | return false; | ||
| 872 | } | ||
| 873 | |||
| 874 | return true; | ||
| 875 | } | ||
| 876 | |||
| 877 | private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) | ||
| 878 | { | ||
| 879 | var paramArg = parameter.Substring(offset); | ||
| 880 | if (paramArg.Length == 0) | ||
| 881 | { | ||
| 882 | this.Messaging.SuppressAllWarnings = true; | ||
| 883 | } | ||
| 884 | else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) | ||
| 885 | { | ||
| 886 | this.Messaging.SuppressWarningMessage(suppressWarning); | ||
| 887 | } | ||
| 888 | else | ||
| 889 | { | ||
| 890 | parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); | ||
| 891 | } | ||
| 892 | } | ||
| 893 | |||
| 894 | private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) | ||
| 895 | { | ||
| 896 | var paramArg = parameter.Substring(offset); | ||
| 897 | if (paramArg.Length == 0) | ||
| 898 | { | ||
| 899 | this.Messaging.WarningsAsError = true; | ||
| 900 | } | ||
| 901 | else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) | ||
| 902 | { | ||
| 903 | this.Messaging.ElevateWarningMessage(elevateWarning); | ||
| 904 | } | ||
| 905 | else | ||
| 906 | { | ||
| 907 | parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); | ||
| 908 | } | ||
| 909 | } | ||
| 910 | } | ||
| 911 | } | ||
| 912 | } | ||
diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLine.cs b/src/wix/WixToolset.Core/CommandLine/CommandLine.cs new file mode 100644 index 00000000..b87b6a5d --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLine.cs | |||
| @@ -0,0 +1,199 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.CommandLine | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Extensibility; | ||
| 8 | using WixToolset.Extensibility.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | internal enum CommandTypes | ||
| 12 | { | ||
| 13 | Unknown, | ||
| 14 | Build, | ||
| 15 | Preprocess, | ||
| 16 | Compile, | ||
| 17 | Link, | ||
| 18 | Bind, | ||
| 19 | Decompile, | ||
| 20 | } | ||
| 21 | |||
| 22 | internal class CommandLine : ICommandLine | ||
| 23 | { | ||
| 24 | public CommandLine(IServiceProvider serviceProvider) => this.ServiceProvider = serviceProvider; | ||
| 25 | |||
| 26 | private IServiceProvider ServiceProvider { get; } | ||
| 27 | |||
| 28 | public ICommandLineCommand CreateCommand(string[] args) | ||
| 29 | { | ||
| 30 | var arguments = this.ServiceProvider.GetService<ICommandLineArguments>(); | ||
| 31 | arguments.Populate(args); | ||
| 32 | |||
| 33 | this.LoadExtensions(arguments.Extensions); | ||
| 34 | |||
| 35 | return this.ParseStandardCommandLine(arguments); | ||
| 36 | } | ||
| 37 | |||
| 38 | public ICommandLineCommand CreateCommand(string commandLine) | ||
| 39 | { | ||
| 40 | var arguments = this.ServiceProvider.GetService<ICommandLineArguments>(); | ||
| 41 | arguments.Populate(commandLine); | ||
| 42 | |||
| 43 | this.LoadExtensions(arguments.Extensions); | ||
| 44 | |||
| 45 | return this.ParseStandardCommandLine(arguments); | ||
| 46 | } | ||
| 47 | |||
| 48 | public ICommandLineCommand ParseStandardCommandLine(ICommandLineArguments arguments) | ||
| 49 | { | ||
| 50 | var context = this.ServiceProvider.GetService<ICommandLineContext>(); | ||
| 51 | context.ExtensionManager = this.ServiceProvider.GetService<IExtensionManager>(); | ||
| 52 | context.Arguments = arguments; | ||
| 53 | |||
| 54 | var command = this.Parse(context); | ||
| 55 | |||
| 56 | if (command.ShowLogo) | ||
| 57 | { | ||
| 58 | var branding = this.ServiceProvider.GetService<IWixBranding>(); | ||
| 59 | Console.WriteLine(branding.ReplacePlaceholders("[AssemblyProduct] [AssemblyDescription] version [FileVersion]")); | ||
| 60 | Console.WriteLine(branding.ReplacePlaceholders("[AssemblyCopyright]")); | ||
| 61 | } | ||
| 62 | |||
| 63 | return command; | ||
| 64 | } | ||
| 65 | |||
| 66 | private void LoadExtensions(string[] extensions) | ||
| 67 | { | ||
| 68 | var extensionManager = this.ServiceProvider.GetService<IExtensionManager>(); | ||
| 69 | |||
| 70 | foreach (var extension in extensions) | ||
| 71 | { | ||
| 72 | extensionManager.Load(extension); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | private ICommandLineCommand Parse(ICommandLineContext context) | ||
| 77 | { | ||
| 78 | var branding = context.ServiceProvider.GetService<IWixBranding>(); | ||
| 79 | var extensions = context.ExtensionManager.GetServices<IExtensionCommandLine>(); | ||
| 80 | |||
| 81 | foreach (var extension in extensions) | ||
| 82 | { | ||
| 83 | extension.PreParse(context); | ||
| 84 | } | ||
| 85 | |||
| 86 | ICommandLineCommand command = null; | ||
| 87 | var parser = context.Arguments.Parse(); | ||
| 88 | |||
| 89 | while (command?.StopParsing != true && | ||
| 90 | String.IsNullOrEmpty(parser.ErrorArgument) && | ||
| 91 | parser.TryGetNextSwitchOrArgument(out var arg)) | ||
| 92 | { | ||
| 93 | if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments. | ||
| 94 | { | ||
| 95 | continue; | ||
| 96 | } | ||
| 97 | |||
| 98 | // First argument must be the command or global switch (that creates a command). | ||
| 99 | if (command == null) | ||
| 100 | { | ||
| 101 | if (!this.TryParseCommand(arg, parser, extensions, out command)) | ||
| 102 | { | ||
| 103 | parser.ReportErrorArgument(arg); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | else if (parser.IsSwitch(arg)) | ||
| 107 | { | ||
| 108 | if (!command.TryParseArgument(parser, arg) && !TryParseCommandLineArgumentWithExtension(arg, parser, extensions)) | ||
| 109 | { | ||
| 110 | parser.ReportErrorArgument(arg); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | else if (!TryParseCommandLineArgumentWithExtension(arg, parser, extensions) && !command.TryParseArgument(parser, arg)) | ||
| 114 | { | ||
| 115 | parser.ReportErrorArgument(arg); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | foreach (var extension in extensions) | ||
| 120 | { | ||
| 121 | extension.PostParse(); | ||
| 122 | } | ||
| 123 | |||
| 124 | return command ?? new HelpCommand(extensions, branding); | ||
| 125 | } | ||
| 126 | |||
| 127 | private bool TryParseCommand(string arg, ICommandLineParser parser, IEnumerable<IExtensionCommandLine> extensions, out ICommandLineCommand command) | ||
| 128 | { | ||
| 129 | command = null; | ||
| 130 | |||
| 131 | if (parser.IsSwitch(arg)) | ||
| 132 | { | ||
| 133 | var parameter = arg.Substring(1); | ||
| 134 | switch (parameter.ToLowerInvariant()) | ||
| 135 | { | ||
| 136 | case "?": | ||
| 137 | case "h": | ||
| 138 | case "help": | ||
| 139 | case "-help": | ||
| 140 | var branding = this.ServiceProvider.GetService<IWixBranding>(); | ||
| 141 | command = new HelpCommand(extensions, branding); | ||
| 142 | break; | ||
| 143 | |||
| 144 | case "version": | ||
| 145 | case "-version": | ||
| 146 | command = new VersionCommand(); | ||
| 147 | break; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | else | ||
| 151 | { | ||
| 152 | if (Enum.TryParse(arg, true, out CommandTypes commandType)) | ||
| 153 | { | ||
| 154 | switch (commandType) | ||
| 155 | { | ||
| 156 | case CommandTypes.Build: | ||
| 157 | command = new BuildCommand(this.ServiceProvider); | ||
| 158 | break; | ||
| 159 | |||
| 160 | case CommandTypes.Compile: | ||
| 161 | command = new CompileCommand(this.ServiceProvider); | ||
| 162 | break; | ||
| 163 | |||
| 164 | case CommandTypes.Decompile: | ||
| 165 | command = new DecompileCommand(this.ServiceProvider); | ||
| 166 | break; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | else | ||
| 170 | { | ||
| 171 | foreach (var extension in extensions) | ||
| 172 | { | ||
| 173 | if (extension.TryParseCommand(parser, arg, out command)) | ||
| 174 | { | ||
| 175 | break; | ||
| 176 | } | ||
| 177 | |||
| 178 | command = null; | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | return command != null; | ||
| 184 | } | ||
| 185 | |||
| 186 | private static bool TryParseCommandLineArgumentWithExtension(string arg, ICommandLineParser parse, IEnumerable<IExtensionCommandLine> extensions) | ||
| 187 | { | ||
| 188 | foreach (var extension in extensions) | ||
| 189 | { | ||
| 190 | if (extension.TryParseArgument(parse, arg)) | ||
| 191 | { | ||
| 192 | return true; | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | return false; | ||
| 197 | } | ||
| 198 | } | ||
| 199 | } | ||
diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs b/src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs new file mode 100644 index 00000000..40b8b320 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs | |||
| @@ -0,0 +1,207 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.CommandLine | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Text; | ||
| 9 | using System.Text.RegularExpressions; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class CommandLineArguments : ICommandLineArguments | ||
| 14 | { | ||
| 15 | public CommandLineArguments(IServiceProvider serviceProvider) | ||
| 16 | { | ||
| 17 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 18 | } | ||
| 19 | |||
| 20 | public string[] OriginalArguments { get; set; } | ||
| 21 | |||
| 22 | public string[] Arguments { get; set; } | ||
| 23 | |||
| 24 | public string[] Extensions { get; set; } | ||
| 25 | |||
| 26 | public string ErrorArgument { get; set; } | ||
| 27 | |||
| 28 | private IMessaging Messaging { get; } | ||
| 29 | |||
| 30 | public void Populate(string commandLine) | ||
| 31 | { | ||
| 32 | var args = CommandLineArguments.ParseArgumentsToArray(commandLine); | ||
| 33 | |||
| 34 | this.Populate(args.ToArray()); | ||
| 35 | } | ||
| 36 | |||
| 37 | public void Populate(string[] args) | ||
| 38 | { | ||
| 39 | this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(args); | ||
| 40 | |||
| 41 | this.ProcessArgumentsAndParseExtensions(this.OriginalArguments); | ||
| 42 | } | ||
| 43 | |||
| 44 | public ICommandLineParser Parse() => new CommandLineParser(this.Messaging, this.Arguments, this.ErrorArgument); | ||
| 45 | |||
| 46 | private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments) | ||
| 47 | { | ||
| 48 | var args = new List<string>(); | ||
| 49 | |||
| 50 | foreach (var arg in commandLineArguments) | ||
| 51 | { | ||
| 52 | if (arg != null) | ||
| 53 | { | ||
| 54 | if ('@' == arg[0]) | ||
| 55 | { | ||
| 56 | var responseFileArguments = CommandLineArguments.ParseResponseFile(arg.Substring(1)); | ||
| 57 | args.AddRange(responseFileArguments); | ||
| 58 | } | ||
| 59 | else | ||
| 60 | { | ||
| 61 | args.Add(arg); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | this.OriginalArguments = args.ToArray(); | ||
| 67 | } | ||
| 68 | |||
| 69 | private void ProcessArgumentsAndParseExtensions(string[] args) | ||
| 70 | { | ||
| 71 | var arguments = new List<string>(); | ||
| 72 | var extensions = new List<string>(); | ||
| 73 | |||
| 74 | for (var i = 0; i < args.Length; ++i) | ||
| 75 | { | ||
| 76 | var arg = args[i]; | ||
| 77 | |||
| 78 | if ("-ext" == arg || "/ext" == arg) | ||
| 79 | { | ||
| 80 | if (!CommandLineArguments.IsSwitchAt(args, ++i)) | ||
| 81 | { | ||
| 82 | extensions.Add(args[i]); | ||
| 83 | } | ||
| 84 | else | ||
| 85 | { | ||
| 86 | this.ErrorArgument = arg; | ||
| 87 | break; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | else | ||
| 91 | { | ||
| 92 | arguments.Add(arg); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | this.Arguments = arguments.ToArray(); | ||
| 97 | this.Extensions = extensions.ToArray(); | ||
| 98 | } | ||
| 99 | |||
| 100 | private static List<string> ParseResponseFile(string responseFile) | ||
| 101 | { | ||
| 102 | string arguments; | ||
| 103 | |||
| 104 | using (var reader = new StreamReader(responseFile)) | ||
| 105 | { | ||
| 106 | arguments = reader.ReadToEnd(); | ||
| 107 | } | ||
| 108 | |||
| 109 | return CommandLineArguments.ParseArgumentsToArray(arguments); | ||
| 110 | } | ||
| 111 | |||
| 112 | private static List<string> ParseArgumentsToArray(string arguments) | ||
| 113 | { | ||
| 114 | // Scan and parse the arguments string, dividing up the arguments based on whitespace. | ||
| 115 | // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed. | ||
| 116 | // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments. | ||
| 117 | // Escaped quotes and escaped backslashes also need to be unescaped by this process. | ||
| 118 | |||
| 119 | // Collects the final list of arguments to be returned. | ||
| 120 | var argsList = new List<string>(); | ||
| 121 | |||
| 122 | // True if we are inside an unescaped quote, meaning whitespace should be ignored. | ||
| 123 | var insideQuote = false; | ||
| 124 | |||
| 125 | // Index of the start of the current argument substring; either the start of the argument | ||
| 126 | // or the start of a quoted or unquoted sequence within it. | ||
| 127 | var partStart = 0; | ||
| 128 | |||
| 129 | // The current argument string being built; when completed it will be added to the list. | ||
| 130 | var arg = new StringBuilder(); | ||
| 131 | |||
| 132 | for (var i = 0; i <= arguments.Length; i++) | ||
| 133 | { | ||
| 134 | if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote)) | ||
| 135 | { | ||
| 136 | // Reached a whitespace separator or the end of the string. | ||
| 137 | |||
| 138 | // Finish building the current argument. | ||
| 139 | arg.Append(arguments.Substring(partStart, i - partStart)); | ||
| 140 | |||
| 141 | // Skip over the whitespace character. | ||
| 142 | partStart = i + 1; | ||
| 143 | |||
| 144 | // Add the argument to the list if it's not empty. | ||
| 145 | if (arg.Length > 0) | ||
| 146 | { | ||
| 147 | argsList.Add(CommandLineArguments.ExpandEnvironmentVariables(arg.ToString())); | ||
| 148 | arg.Length = 0; | ||
| 149 | } | ||
| 150 | } | ||
| 151 | else if (i > partStart && arguments[i - 1] == '\\') | ||
| 152 | { | ||
| 153 | // Check the character following an unprocessed backslash. | ||
| 154 | // Unescape quotes, and backslashes followed by a quote. | ||
| 155 | if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"')) | ||
| 156 | { | ||
| 157 | // Unescape the quote or backslash by skipping the preceeding backslash. | ||
| 158 | arg.Append(arguments.Substring(partStart, i - 1 - partStart)); | ||
| 159 | arg.Append(arguments[i]); | ||
| 160 | partStart = i + 1; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | else if (arguments[i] == '"') | ||
| 164 | { | ||
| 165 | // Add the quoted or unquoted section to the argument string. | ||
| 166 | arg.Append(arguments.Substring(partStart, i - partStart)); | ||
| 167 | |||
| 168 | // And skip over the quote character. | ||
| 169 | partStart = i + 1; | ||
| 170 | |||
| 171 | insideQuote = !insideQuote; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | return argsList; | ||
| 176 | } | ||
| 177 | |||
| 178 | private static string ExpandEnvironmentVariables(string arguments) | ||
| 179 | { | ||
| 180 | var id = Environment.GetEnvironmentVariables(); | ||
| 181 | |||
| 182 | var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)"); | ||
| 183 | var matches = regex.Matches(arguments); | ||
| 184 | |||
| 185 | var value = String.Empty; | ||
| 186 | for (var i = 0; i <= (matches.Count - 1); i++) | ||
| 187 | { | ||
| 188 | try | ||
| 189 | { | ||
| 190 | var key = matches[i].Value; | ||
| 191 | regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)")); | ||
| 192 | value = id[key].ToString(); | ||
| 193 | arguments = regex.Replace(arguments, value); | ||
| 194 | } | ||
| 195 | catch (NullReferenceException) | ||
| 196 | { | ||
| 197 | // Collapse unresolved environment variables. | ||
| 198 | arguments = regex.Replace(arguments, value); | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | return arguments; | ||
| 203 | } | ||
| 204 | |||
| 205 | private static bool IsSwitchAt(string[] args, int index) => args.Length > index && !String.IsNullOrEmpty(args[index]) && ('/' == args[index][0] || '-' == args[index][0]); | ||
| 206 | } | ||
| 207 | } | ||
diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs b/src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs new file mode 100644 index 00000000..8d5cf120 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.CommandLine | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | using WixToolset.Extensibility.Services; | ||
| 8 | |||
| 9 | internal class CommandLineContext : ICommandLineContext | ||
| 10 | { | ||
| 11 | public CommandLineContext(IServiceProvider serviceProvider) | ||
| 12 | { | ||
| 13 | this.ServiceProvider = serviceProvider; | ||
| 14 | } | ||
| 15 | |||
| 16 | public IServiceProvider ServiceProvider { get; } | ||
| 17 | |||
| 18 | public IExtensionManager ExtensionManager { get; set; } | ||
| 19 | |||
| 20 | public ICommandLineArguments Arguments { get; set; } | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs b/src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs new file mode 100644 index 00000000..015d3e62 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs | |||
| @@ -0,0 +1,270 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.CommandLine | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | internal class CommandLineParser : ICommandLineParser | ||
| 12 | { | ||
| 13 | private const string ExpectedArgument = "expected argument"; | ||
| 14 | |||
| 15 | public string ErrorArgument { get; private set; } | ||
| 16 | |||
| 17 | private Queue<string> RemainingArguments { get; } | ||
| 18 | |||
| 19 | private IMessaging Messaging { get; } | ||
| 20 | |||
| 21 | public CommandLineParser(IMessaging messaging, string[] arguments, string errorArgument) | ||
| 22 | { | ||
| 23 | this.Messaging = messaging; | ||
| 24 | this.RemainingArguments = new Queue<string>(arguments); | ||
| 25 | this.ErrorArgument = errorArgument; | ||
| 26 | } | ||
| 27 | |||
| 28 | public bool IsSwitch(string arg) | ||
| 29 | { | ||
| 30 | return !String.IsNullOrEmpty(arg) && '-' == arg[0]; | ||
| 31 | } | ||
| 32 | |||
| 33 | public string GetArgumentAsFilePathOrError(string argument, string fileType) | ||
| 34 | { | ||
| 35 | if (!File.Exists(argument)) | ||
| 36 | { | ||
| 37 | this.Messaging.Write(ErrorMessages.FileNotFound(null, argument, fileType)); | ||
| 38 | return null; | ||
| 39 | } | ||
| 40 | |||
| 41 | return argument; | ||
| 42 | } | ||
| 43 | |||
| 44 | public void GetArgumentAsFilePathOrError(string argument, string fileType, IList<string> paths) | ||
| 45 | { | ||
| 46 | foreach (var path in this.GetFiles(argument, fileType)) | ||
| 47 | { | ||
| 48 | paths.Add(path); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | public string GetNextArgumentOrError(string commandLineSwitch) | ||
| 53 | { | ||
| 54 | if (this.TryGetNextNonSwitchArgumentOrError(out var argument)) | ||
| 55 | { | ||
| 56 | return argument; | ||
| 57 | } | ||
| 58 | |||
| 59 | this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); | ||
| 60 | return null; | ||
| 61 | } | ||
| 62 | |||
| 63 | public bool GetNextArgumentOrError(string commandLineSwitch, IList<string> args) | ||
| 64 | { | ||
| 65 | if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) | ||
| 66 | { | ||
| 67 | args.Add(arg); | ||
| 68 | return true; | ||
| 69 | } | ||
| 70 | |||
| 71 | this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); | ||
| 72 | return false; | ||
| 73 | } | ||
| 74 | |||
| 75 | public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch) | ||
| 76 | { | ||
| 77 | if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) | ||
| 78 | { | ||
| 79 | return directory; | ||
| 80 | } | ||
| 81 | |||
| 82 | this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); | ||
| 83 | return null; | ||
| 84 | } | ||
| 85 | |||
| 86 | public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList<string> directories) | ||
| 87 | { | ||
| 88 | if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) | ||
| 89 | { | ||
| 90 | directories.Add(directory); | ||
| 91 | return true; | ||
| 92 | } | ||
| 93 | |||
| 94 | this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); | ||
| 95 | return false; | ||
| 96 | } | ||
| 97 | |||
| 98 | public string GetNextArgumentAsFilePathOrError(string commandLineSwitch) | ||
| 99 | { | ||
| 100 | if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path)) | ||
| 101 | { | ||
| 102 | return path; | ||
| 103 | } | ||
| 104 | |||
| 105 | this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); | ||
| 106 | return null; | ||
| 107 | } | ||
| 108 | |||
| 109 | public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList<string> paths) | ||
| 110 | { | ||
| 111 | if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) | ||
| 112 | { | ||
| 113 | foreach (var path in this.GetFiles(arg, fileType)) | ||
| 114 | { | ||
| 115 | paths.Add(path); | ||
| 116 | } | ||
| 117 | |||
| 118 | return true; | ||
| 119 | } | ||
| 120 | |||
| 121 | this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); | ||
| 122 | return false; | ||
| 123 | } | ||
| 124 | |||
| 125 | public void ReportErrorArgument(string argument, Message message = null) | ||
| 126 | { | ||
| 127 | this.Messaging.Write(message ?? ErrorMessages.AdditionalArgumentUnexpected(argument)); | ||
| 128 | this.ErrorArgument = argument; | ||
| 129 | } | ||
| 130 | |||
| 131 | public bool TryGetNextSwitchOrArgument(out string arg) | ||
| 132 | { | ||
| 133 | if (this.RemainingArguments.Count > 0) | ||
| 134 | { | ||
| 135 | arg = this.RemainingArguments.Dequeue(); | ||
| 136 | return true; | ||
| 137 | } | ||
| 138 | |||
| 139 | arg = null; | ||
| 140 | return false; | ||
| 141 | } | ||
| 142 | |||
| 143 | private bool TryGetNextNonSwitchArgumentOrError(out string arg) | ||
| 144 | { | ||
| 145 | var result = this.TryGetNextSwitchOrArgument(out arg); | ||
| 146 | |||
| 147 | if (!result || this.IsSwitch(arg)) | ||
| 148 | { | ||
| 149 | this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument; | ||
| 150 | return false; | ||
| 151 | } | ||
| 152 | |||
| 153 | return result; | ||
| 154 | } | ||
| 155 | |||
| 156 | private bool TryGetDirectory(string commandlineSwitch, string arg, out string directory) | ||
| 157 | { | ||
| 158 | directory = null; | ||
| 159 | |||
| 160 | if (File.Exists(arg)) | ||
| 161 | { | ||
| 162 | this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg)); | ||
| 163 | return false; | ||
| 164 | } | ||
| 165 | |||
| 166 | directory = this.VerifyPath(arg); | ||
| 167 | return directory != null; | ||
| 168 | } | ||
| 169 | |||
| 170 | private bool TryGetFile(string commandlineSwitch, string arg, out string path) | ||
| 171 | { | ||
| 172 | path = null; | ||
| 173 | |||
| 174 | if (String.IsNullOrEmpty(arg) || '-' == arg[0]) | ||
| 175 | { | ||
| 176 | this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch)); | ||
| 177 | } | ||
| 178 | else if (Directory.Exists(arg)) | ||
| 179 | { | ||
| 180 | this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg)); | ||
| 181 | } | ||
| 182 | else | ||
| 183 | { | ||
| 184 | path = this.VerifyPath(arg); | ||
| 185 | } | ||
| 186 | |||
| 187 | return path != null; | ||
| 188 | } | ||
| 189 | |||
| 190 | /// <summary> | ||
| 191 | /// Get a set of files that possibly have a search pattern in the path (such as '*'). | ||
| 192 | /// </summary> | ||
| 193 | /// <param name="searchPath">Search path to find files in.</param> | ||
| 194 | /// <param name="fileType">Type of file; typically "Source".</param> | ||
| 195 | /// <returns>An array of files matching the search path.</returns> | ||
| 196 | /// <remarks> | ||
| 197 | /// This method is written in this verbose way because it needs to support ".." in the path. | ||
| 198 | /// It needs the directory path isolated from the file name in order to use Directory.GetFiles | ||
| 199 | /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since | ||
| 200 | /// Path.GetDirectoryName does not support ".." in the path. | ||
| 201 | /// </remarks> | ||
| 202 | private string[] GetFiles(string searchPath, string fileType) | ||
| 203 | { | ||
| 204 | if (null == searchPath) | ||
| 205 | { | ||
| 206 | throw new ArgumentNullException(nameof(searchPath)); | ||
| 207 | } | ||
| 208 | |||
| 209 | // Convert alternate directory separators to the standard one. | ||
| 210 | var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); | ||
| 211 | var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); | ||
| 212 | var files = new string[0]; | ||
| 213 | |||
| 214 | try | ||
| 215 | { | ||
| 216 | if (0 > lastSeparator) | ||
| 217 | { | ||
| 218 | files = Directory.GetFiles(".", filePath); | ||
| 219 | } | ||
| 220 | else // found directory separator | ||
| 221 | { | ||
| 222 | files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1)); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | catch (DirectoryNotFoundException) | ||
| 226 | { | ||
| 227 | // Don't let this function throw the DirectoryNotFoundException. This exception | ||
| 228 | // occurs for non-existant directories and invalid characters in the searchPattern. | ||
| 229 | } | ||
| 230 | catch (ArgumentException) | ||
| 231 | { | ||
| 232 | // Don't let this function throw the ArgumentException. This exception | ||
| 233 | // occurs in certain situations such as when passing a malformed UNC path. | ||
| 234 | } | ||
| 235 | catch (IOException) | ||
| 236 | { | ||
| 237 | } | ||
| 238 | |||
| 239 | if (0 == files.Length) | ||
| 240 | { | ||
| 241 | this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType)); | ||
| 242 | } | ||
| 243 | |||
| 244 | return files; | ||
| 245 | } | ||
| 246 | |||
| 247 | private string VerifyPath(string path) | ||
| 248 | { | ||
| 249 | string fullPath; | ||
| 250 | |||
| 251 | if (0 <= path.IndexOf('\"')) | ||
| 252 | { | ||
| 253 | this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path)); | ||
| 254 | return null; | ||
| 255 | } | ||
| 256 | |||
| 257 | try | ||
| 258 | { | ||
| 259 | fullPath = Path.GetFullPath(path); | ||
| 260 | } | ||
| 261 | catch (Exception e) | ||
| 262 | { | ||
| 263 | this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message)); | ||
| 264 | return null; | ||
| 265 | } | ||
| 266 | |||
| 267 | return fullPath; | ||
| 268 | } | ||
| 269 | } | ||
| 270 | } | ||
diff --git a/src/wix/WixToolset.Core/CommandLine/CompileCommand.cs b/src/wix/WixToolset.Core/CommandLine/CompileCommand.cs new file mode 100644 index 00000000..6e31b241 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CompileCommand.cs | |||
| @@ -0,0 +1,94 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.CommandLine | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Threading; | ||
| 8 | using System.Threading.Tasks; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class CompileCommand : ICommandLineCommand | ||
| 15 | { | ||
| 16 | public CompileCommand(IServiceProvider serviceProvider) | ||
| 17 | { | ||
| 18 | this.ServiceProvider = serviceProvider; | ||
| 19 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 20 | this.ExtensionManager = serviceProvider.GetService<IExtensionManager>(); | ||
| 21 | } | ||
| 22 | |||
| 23 | public CompileCommand(IServiceProvider serviceProvider, IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, Platform platform) | ||
| 24 | { | ||
| 25 | this.ServiceProvider = serviceProvider; | ||
| 26 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 27 | this.ExtensionManager = serviceProvider.GetService<IExtensionManager>(); | ||
| 28 | this.SourceFiles = sources; | ||
| 29 | this.PreprocessorVariables = preprocessorVariables; | ||
| 30 | this.Platform = platform; | ||
| 31 | } | ||
| 32 | |||
| 33 | private IServiceProvider ServiceProvider { get; } | ||
| 34 | |||
| 35 | public IMessaging Messaging { get; } | ||
| 36 | |||
| 37 | public IExtensionManager ExtensionManager { get; } | ||
| 38 | |||
| 39 | private IEnumerable<SourceFile> SourceFiles { get; } | ||
| 40 | |||
| 41 | private IDictionary<string, string> PreprocessorVariables { get; } | ||
| 42 | |||
| 43 | private Platform Platform { get; } | ||
| 44 | |||
| 45 | public IReadOnlyCollection<string> IncludeSearchPaths { get; } | ||
| 46 | |||
| 47 | public bool ShowLogo => throw new NotImplementedException(); | ||
| 48 | |||
| 49 | public bool StopParsing => throw new NotImplementedException(); | ||
| 50 | |||
| 51 | public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => throw new NotImplementedException(); | ||
| 52 | |||
| 53 | public Task<int> ExecuteAsync(CancellationToken _) | ||
| 54 | { | ||
| 55 | foreach (var sourceFile in this.SourceFiles) | ||
| 56 | { | ||
| 57 | var context = this.ServiceProvider.GetService<IPreprocessContext>(); | ||
| 58 | context.Extensions = this.ExtensionManager.GetServices<IPreprocessorExtension>(); | ||
| 59 | context.Platform = this.Platform; | ||
| 60 | context.IncludeSearchPaths = this.IncludeSearchPaths; | ||
| 61 | context.SourcePath = sourceFile.SourcePath; | ||
| 62 | context.Variables = this.PreprocessorVariables; | ||
| 63 | |||
| 64 | IPreprocessResult result = null; | ||
| 65 | try | ||
| 66 | { | ||
| 67 | var preprocessor = this.ServiceProvider.GetService<IPreprocessor>(); | ||
| 68 | result = preprocessor.Preprocess(context); | ||
| 69 | } | ||
| 70 | catch (WixException e) | ||
| 71 | { | ||
| 72 | this.Messaging.Write(e.Error); | ||
| 73 | } | ||
| 74 | |||
| 75 | if (this.Messaging.EncounteredError) | ||
| 76 | { | ||
| 77 | continue; | ||
| 78 | } | ||
| 79 | |||
| 80 | var compileContext = this.ServiceProvider.GetService<ICompileContext>(); | ||
| 81 | compileContext.Extensions = this.ExtensionManager.GetServices<ICompilerExtension>(); | ||
| 82 | compileContext.Platform = this.Platform; | ||
| 83 | compileContext.Source = result?.Document; | ||
| 84 | |||
| 85 | var compiler = this.ServiceProvider.GetService<ICompiler>(); | ||
| 86 | var intermediate = compiler.Compile(compileContext); | ||
| 87 | |||
| 88 | intermediate.Save(sourceFile.OutputPath); | ||
| 89 | } | ||
| 90 | |||
| 91 | return Task.FromResult(0); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
diff --git a/src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs b/src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs new file mode 100644 index 00000000..fc0ab0c9 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs | |||
| @@ -0,0 +1,256 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.CommandLine | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Threading; | ||
| 8 | using System.Threading.Tasks; | ||
| 9 | using System.Xml.Linq; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Extensibility; | ||
| 12 | using WixToolset.Extensibility.Data; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | internal class DecompileCommand : ICommandLineCommand | ||
| 16 | { | ||
| 17 | private readonly CommandLine commandLine; | ||
| 18 | |||
| 19 | public DecompileCommand(IServiceProvider serviceProvider) | ||
| 20 | { | ||
| 21 | this.ServiceProvider = serviceProvider; | ||
| 22 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 23 | this.commandLine = new CommandLine(this.Messaging); | ||
| 24 | } | ||
| 25 | |||
| 26 | public bool ShowLogo => this.commandLine.ShowLogo; | ||
| 27 | |||
| 28 | public bool StopParsing => this.commandLine.ShowHelp; | ||
| 29 | |||
| 30 | private IServiceProvider ServiceProvider { get; } | ||
| 31 | |||
| 32 | public IMessaging Messaging { get; } | ||
| 33 | |||
| 34 | public Task<int> ExecuteAsync(CancellationToken _) | ||
| 35 | { | ||
| 36 | if (this.commandLine.ShowHelp || String.IsNullOrEmpty(this.commandLine.DecompileFilePath)) | ||
| 37 | { | ||
| 38 | Console.WriteLine("TODO: Show decompile command help"); | ||
| 39 | return Task.FromResult(-1); | ||
| 40 | } | ||
| 41 | |||
| 42 | var context = this.ServiceProvider.GetService<IDecompileContext>(); | ||
| 43 | context.Extensions = this.ServiceProvider.GetService<IExtensionManager>().GetServices<IDecompilerExtension>(); | ||
| 44 | context.DecompilePath = this.commandLine.DecompileFilePath; | ||
| 45 | context.DecompileType = this.commandLine.CalculateDecompileType(); | ||
| 46 | context.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); | ||
| 47 | context.OutputPath = this.commandLine.CalculateOutputPath(); | ||
| 48 | |||
| 49 | try | ||
| 50 | { | ||
| 51 | var decompiler = this.ServiceProvider.GetService<IDecompiler>(); | ||
| 52 | var result = decompiler.Decompile(context); | ||
| 53 | |||
| 54 | if (!this.Messaging.EncounteredError) | ||
| 55 | { | ||
| 56 | Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(context.OutputPath))); | ||
| 57 | result.Document.Save(context.OutputPath, SaveOptions.OmitDuplicateNamespaces); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | catch (WixException e) | ||
| 61 | { | ||
| 62 | this.Messaging.Write(e.Error); | ||
| 63 | } | ||
| 64 | |||
| 65 | if (this.Messaging.EncounteredError) | ||
| 66 | { | ||
| 67 | return Task.FromResult(1); | ||
| 68 | } | ||
| 69 | |||
| 70 | return Task.FromResult(0); | ||
| 71 | } | ||
| 72 | |||
| 73 | public bool TryParseArgument(ICommandLineParser parser, string argument) | ||
| 74 | { | ||
| 75 | return this.commandLine.TryParseArgument(argument, parser); | ||
| 76 | } | ||
| 77 | |||
| 78 | private class CommandLine | ||
| 79 | { | ||
| 80 | public CommandLine(IMessaging messaging) | ||
| 81 | { | ||
| 82 | this.Messaging = messaging; | ||
| 83 | } | ||
| 84 | |||
| 85 | private IMessaging Messaging { get; } | ||
| 86 | |||
| 87 | public string DecompileFilePath { get; private set; } | ||
| 88 | |||
| 89 | public string DecompileType { get; private set; } | ||
| 90 | |||
| 91 | public Platform Platform { get; private set; } | ||
| 92 | |||
| 93 | public bool ShowLogo { get; private set; } | ||
| 94 | |||
| 95 | public bool ShowHelp { get; private set; } | ||
| 96 | |||
| 97 | public string IntermediateFolder { get; private set; } | ||
| 98 | |||
| 99 | public string OutputFile { get; private set; } | ||
| 100 | |||
| 101 | public bool TryParseArgument(string arg, ICommandLineParser parser) | ||
| 102 | { | ||
| 103 | if (parser.IsSwitch(arg)) | ||
| 104 | { | ||
| 105 | var parameter = arg.Substring(1); | ||
| 106 | switch (parameter.ToLowerInvariant()) | ||
| 107 | { | ||
| 108 | case "?": | ||
| 109 | case "h": | ||
| 110 | case "help": | ||
| 111 | this.ShowHelp = true; | ||
| 112 | return true; | ||
| 113 | |||
| 114 | case "intermediatefolder": | ||
| 115 | this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); | ||
| 116 | return true; | ||
| 117 | |||
| 118 | case "o": | ||
| 119 | case "out": | ||
| 120 | this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); | ||
| 121 | return true; | ||
| 122 | |||
| 123 | case "nologo": | ||
| 124 | this.ShowLogo = false; | ||
| 125 | return true; | ||
| 126 | |||
| 127 | case "v": | ||
| 128 | case "verbose": | ||
| 129 | this.Messaging.ShowVerboseMessages = true; | ||
| 130 | return true; | ||
| 131 | } | ||
| 132 | |||
| 133 | if (parameter.StartsWith("sw")) | ||
| 134 | { | ||
| 135 | this.ParseSuppressWarning(parameter, "sw".Length, parser); | ||
| 136 | return true; | ||
| 137 | } | ||
| 138 | else if (parameter.StartsWith("suppresswarning")) | ||
| 139 | { | ||
| 140 | this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); | ||
| 141 | return true; | ||
| 142 | } | ||
| 143 | else if (parameter.StartsWith("wx")) | ||
| 144 | { | ||
| 145 | this.ParseWarningAsError(parameter, "wx".Length, parser); | ||
| 146 | return true; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | else | ||
| 150 | { | ||
| 151 | if (String.IsNullOrEmpty(this.DecompileFilePath)) | ||
| 152 | { | ||
| 153 | this.DecompileFilePath = parser.GetArgumentAsFilePathOrError(arg, "decompile file"); | ||
| 154 | return true; | ||
| 155 | } | ||
| 156 | else if (String.IsNullOrEmpty(this.OutputFile)) | ||
| 157 | { | ||
| 158 | this.OutputFile = parser.GetArgumentAsFilePathOrError(arg, "output file"); | ||
| 159 | return true; | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | return false; | ||
| 164 | } | ||
| 165 | |||
| 166 | public OutputType CalculateDecompileType() | ||
| 167 | { | ||
| 168 | if (String.IsNullOrEmpty(this.DecompileType)) | ||
| 169 | { | ||
| 170 | this.DecompileType = Path.GetExtension(this.DecompileFilePath); | ||
| 171 | } | ||
| 172 | |||
| 173 | switch (this.DecompileType.ToLowerInvariant()) | ||
| 174 | { | ||
| 175 | case "bundle": | ||
| 176 | case ".exe": | ||
| 177 | return OutputType.Bundle; | ||
| 178 | |||
| 179 | case "library": | ||
| 180 | case ".wixlib": | ||
| 181 | return OutputType.Library; | ||
| 182 | |||
| 183 | case "module": | ||
| 184 | case ".msm": | ||
| 185 | return OutputType.Module; | ||
| 186 | |||
| 187 | case "patch": | ||
| 188 | case ".msp": | ||
| 189 | return OutputType.Patch; | ||
| 190 | |||
| 191 | case ".pcp": | ||
| 192 | return OutputType.PatchCreation; | ||
| 193 | |||
| 194 | case "product": | ||
| 195 | case "package": | ||
| 196 | case ".msi": | ||
| 197 | return OutputType.Product; | ||
| 198 | |||
| 199 | case "transform": | ||
| 200 | case ".mst": | ||
| 201 | return OutputType.Transform; | ||
| 202 | |||
| 203 | case "intermediatepostlink": | ||
| 204 | case ".wixipl": | ||
| 205 | return OutputType.IntermediatePostLink; | ||
| 206 | } | ||
| 207 | |||
| 208 | return OutputType.Unknown; | ||
| 209 | } | ||
| 210 | |||
| 211 | public string CalculateIntermedateFolder() | ||
| 212 | { | ||
| 213 | return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; | ||
| 214 | } | ||
| 215 | |||
| 216 | public string CalculateOutputPath() | ||
| 217 | { | ||
| 218 | return String.IsNullOrEmpty(this.OutputFile) ? Path.ChangeExtension(this.DecompileFilePath, ".wxs") : this.OutputFile; | ||
| 219 | } | ||
| 220 | |||
| 221 | private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) | ||
| 222 | { | ||
| 223 | var paramArg = parameter.Substring(offset); | ||
| 224 | if (paramArg.Length == 0) | ||
| 225 | { | ||
| 226 | this.Messaging.SuppressAllWarnings = true; | ||
| 227 | } | ||
| 228 | else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) | ||
| 229 | { | ||
| 230 | this.Messaging.SuppressWarningMessage(suppressWarning); | ||
| 231 | } | ||
| 232 | else | ||
| 233 | { | ||
| 234 | parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) | ||
| 239 | { | ||
| 240 | var paramArg = parameter.Substring(offset); | ||
| 241 | if (paramArg.Length == 0) | ||
| 242 | { | ||
| 243 | this.Messaging.WarningsAsError = true; | ||
| 244 | } | ||
| 245 | else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) | ||
| 246 | { | ||
| 247 | this.Messaging.ElevateWarningMessage(elevateWarning); | ||
| 248 | } | ||
| 249 | else | ||
| 250 | { | ||
| 251 | parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); | ||
| 252 | } | ||
| 253 | } | ||
| 254 | } | ||
| 255 | } | ||
| 256 | } | ||
diff --git a/src/wix/WixToolset.Core/CommandLine/HelpCommand.cs b/src/wix/WixToolset.Core/CommandLine/HelpCommand.cs new file mode 100644 index 00000000..6a5ac183 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/HelpCommand.cs | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.CommandLine | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using System.Threading; | ||
| 9 | using System.Threading.Tasks; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class HelpCommand : ICommandLineCommand | ||
| 15 | { | ||
| 16 | private static readonly ExtensionCommandLineSwitch[] BuiltInSwitches = new ExtensionCommandLineSwitch[] | ||
| 17 | { | ||
| 18 | new ExtensionCommandLineSwitch { Switch = "build", Description = "Build a wixlib, package or bundle." }, | ||
| 19 | new ExtensionCommandLineSwitch { Switch = "decompile", Description = "Decompile a package or bundle into source code." }, | ||
| 20 | }; | ||
| 21 | |||
| 22 | public HelpCommand(IEnumerable<IExtensionCommandLine> extensions, IWixBranding branding) | ||
| 23 | { | ||
| 24 | this.Extensions = extensions; | ||
| 25 | this.Branding = branding; | ||
| 26 | } | ||
| 27 | |||
| 28 | public bool ShowLogo => true; | ||
| 29 | |||
| 30 | public bool StopParsing => true; | ||
| 31 | |||
| 32 | private IEnumerable<IExtensionCommandLine> Extensions { get; } | ||
| 33 | |||
| 34 | private IWixBranding Branding { get; } | ||
| 35 | |||
| 36 | public Task<int> ExecuteAsync(CancellationToken _) | ||
| 37 | { | ||
| 38 | var commandLineSwitches = new List<ExtensionCommandLineSwitch>(BuiltInSwitches); | ||
| 39 | commandLineSwitches.AddRange(this.Extensions.SelectMany(e => e.CommandLineSwitches).OrderBy(s => s.Switch, StringComparer.Ordinal)); | ||
| 40 | |||
| 41 | Console.WriteLine(); | ||
| 42 | Console.WriteLine("Usage: wix [option]"); | ||
| 43 | Console.WriteLine("Usage: wix [command]"); | ||
| 44 | Console.WriteLine(); | ||
| 45 | Console.WriteLine("Options:"); | ||
| 46 | Console.WriteLine(" -h|--help Show command line help."); | ||
| 47 | Console.WriteLine(" --version Display WiX Toolset version in use."); | ||
| 48 | Console.WriteLine(); | ||
| 49 | |||
| 50 | Console.WriteLine("Commands:"); | ||
| 51 | foreach (var commandLineSwitch in commandLineSwitches) | ||
| 52 | { | ||
| 53 | Console.WriteLine(" {0,-17} {1}", commandLineSwitch.Switch, commandLineSwitch.Description); | ||
| 54 | } | ||
| 55 | |||
| 56 | Console.WriteLine(); | ||
| 57 | Console.WriteLine("Run 'wix [command] --help' for more information on a command."); | ||
| 58 | Console.WriteLine(); | ||
| 59 | Console.WriteLine(this.Branding.ReplacePlaceholders("For more information see: [SupportUrl]")); | ||
| 60 | |||
| 61 | return Task.FromResult(-1); | ||
| 62 | } | ||
| 63 | |||
| 64 | public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments | ||
| 65 | } | ||
| 66 | } | ||
diff --git a/src/wix/WixToolset.Core/CommandLine/VersionCommand.cs b/src/wix/WixToolset.Core/CommandLine/VersionCommand.cs new file mode 100644 index 00000000..01a7d0e6 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/VersionCommand.cs | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.CommandLine | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Threading; | ||
| 7 | using System.Threading.Tasks; | ||
| 8 | using WixToolset.Extensibility.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | internal class VersionCommand : ICommandLineCommand | ||
| 12 | { | ||
| 13 | public bool ShowLogo => true; | ||
| 14 | |||
| 15 | public bool StopParsing => true; | ||
| 16 | |||
| 17 | public Task<int> ExecuteAsync(CancellationToken cancellationToken) | ||
| 18 | { | ||
| 19 | Console.WriteLine(ThisAssembly.AssemblyInformationalVersion); | ||
| 20 | |||
| 21 | return Task.FromResult(0); | ||
| 22 | } | ||
| 23 | |||
| 24 | public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments | ||
| 25 | } | ||
| 26 | } | ||
diff --git a/src/wix/WixToolset.Core/Common.cs b/src/wix/WixToolset.Core/Common.cs new file mode 100644 index 00000000..848f009a --- /dev/null +++ b/src/wix/WixToolset.Core/Common.cs | |||
| @@ -0,0 +1,832 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Security.Cryptography; | ||
| 11 | using System.Text; | ||
| 12 | using System.Xml; | ||
| 13 | using System.Xml.Linq; | ||
| 14 | using WixToolset.Data; | ||
| 15 | using WixToolset.Extensibility; | ||
| 16 | using WixToolset.Extensibility.Services; | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// Common Wix utility methods and types. | ||
| 20 | /// </summary> | ||
| 21 | internal static class Common | ||
| 22 | { | ||
| 23 | private static readonly char[] IllegalShortFilenameCharacters = new[] { '\\', '?', '|', '>', '<', ':', '/', '*', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; | ||
| 24 | private static readonly char[] IllegalWildcardShortFilenameCharacters = new[] { '\\', '|', '>', '<', ':', '/', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; | ||
| 25 | |||
| 26 | internal static readonly char[] IllegalLongFilenameCharacters = new[] { '\\', '/', '?', '*', '|', '>', '<', ':', '\"' }; // illegal: \ / ? | > < : / * " | ||
| 27 | internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/' | ||
| 28 | internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?' | ||
| 29 | |||
| 30 | public static string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath, IMessaging messageHandler) | ||
| 31 | { | ||
| 32 | const string root = @"C:\"; | ||
| 33 | if (!Path.IsPathRooted(relativePath)) | ||
| 34 | { | ||
| 35 | var normalizedPath = Path.GetFullPath(root + relativePath); | ||
| 36 | if (normalizedPath.StartsWith(root)) | ||
| 37 | { | ||
| 38 | var canonicalizedPath = normalizedPath.Substring(root.Length); | ||
| 39 | if (canonicalizedPath != relativePath) | ||
| 40 | { | ||
| 41 | messageHandler.Write(WarningMessages.PathCanonicalized(sourceLineNumbers, elementName, attributeName, relativePath, canonicalizedPath)); | ||
| 42 | } | ||
| 43 | return canonicalizedPath; | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | messageHandler.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, elementName, attributeName, relativePath)); | ||
| 48 | return relativePath; | ||
| 49 | } | ||
| 50 | |||
| 51 | /// <summary> | ||
| 52 | /// Gets a valid code page from the given web name or integer value. | ||
| 53 | /// </summary> | ||
| 54 | /// <param name="value">A code page web name or integer value as a string.</param> | ||
| 55 | /// <param name="allowNoChange">Whether to allow -1 which does not change the database code pages. This may be the case with wxl files.</param> | ||
| 56 | /// <param name="onlyAnsi">Whether to allow Unicode (UCS) or UTF code pages.</param> | ||
| 57 | /// <param name="sourceLineNumbers">Source line information for the current authoring.</param> | ||
| 58 | /// <returns>A valid code page number.</returns> | ||
| 59 | /// <exception cref="ArgumentOutOfRangeException">The value is an integer less than 0 or greater than 65535.</exception> | ||
| 60 | /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception> | ||
| 61 | /// <exception cref="NotSupportedException">The value doesn't not represent a valid code page name or integer value.</exception> | ||
| 62 | /// <exception cref="WixException">The code page is invalid for summary information.</exception> | ||
| 63 | public static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) | ||
| 64 | { | ||
| 65 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); | ||
| 66 | |||
| 67 | try | ||
| 68 | { | ||
| 69 | Encoding encoding; | ||
| 70 | |||
| 71 | // Check if a integer as a string was passed. | ||
| 72 | if (Int32.TryParse(value, out var codePage)) | ||
| 73 | { | ||
| 74 | if (0 == codePage) | ||
| 75 | { | ||
| 76 | // 0 represents a neutral database | ||
| 77 | return 0; | ||
| 78 | } | ||
| 79 | else if (allowNoChange && -1 == codePage) | ||
| 80 | { | ||
| 81 | // -1 means no change to the database code page | ||
| 82 | return -1; | ||
| 83 | } | ||
| 84 | |||
| 85 | encoding = Encoding.GetEncoding(codePage); | ||
| 86 | } | ||
| 87 | else | ||
| 88 | { | ||
| 89 | encoding = Encoding.GetEncoding(value); | ||
| 90 | } | ||
| 91 | |||
| 92 | // Windows Installer parses some code page references | ||
| 93 | // as unsigned shorts which fail to open the database. | ||
| 94 | if (onlyAnsi) | ||
| 95 | { | ||
| 96 | codePage = encoding.CodePage; | ||
| 97 | if (0 > codePage || Int16.MaxValue < codePage) | ||
| 98 | { | ||
| 99 | throw new WixException(ErrorMessages.InvalidSummaryInfoCodePage(sourceLineNumbers, codePage)); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | if (encoding == null) | ||
| 104 | { | ||
| 105 | throw new WixException(ErrorMessages.IllegalCodepage(sourceLineNumbers, codePage)); | ||
| 106 | } | ||
| 107 | |||
| 108 | return encoding.CodePage; | ||
| 109 | } | ||
| 110 | catch (ArgumentException ex) | ||
| 111 | { | ||
| 112 | // Rethrow as NotSupportedException since either can be thrown | ||
| 113 | // if the system does not support the specified code page. | ||
| 114 | throw new NotSupportedException(ex.Message, ex); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | /// <summary> | ||
| 119 | /// Verifies if an identifier is a valid binder variable name. | ||
| 120 | /// </summary> | ||
| 121 | /// <param name="variable">Binder variable name to verify.</param> | ||
| 122 | /// <returns>True if the identifier is a valid binder variable name.</returns> | ||
| 123 | public static bool IsValidBinderVariable(string variable) | ||
| 124 | { | ||
| 125 | return TryParseWixVariable(variable, 0, out var parsed) && parsed.Index == 0 && parsed.Length == variable.Length && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); | ||
| 126 | } | ||
| 127 | |||
| 128 | /// <summary> | ||
| 129 | /// Verifies if a string contains a valid binder variable name. | ||
| 130 | /// </summary> | ||
| 131 | /// <param name="verify">String to verify.</param> | ||
| 132 | /// <returns>True if the string contains a valid binder variable name.</returns> | ||
| 133 | public static bool ContainsValidBinderVariable(string verify) | ||
| 134 | { | ||
| 135 | return TryParseWixVariable(verify, 0, out var parsed) && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); | ||
| 136 | } | ||
| 137 | |||
| 138 | /// <summary> | ||
| 139 | /// Verifies the given string is a valid 4-part version module or bundle version. | ||
| 140 | /// </summary> | ||
| 141 | /// <param name="version">The version to verify.</param> | ||
| 142 | /// <returns>True if version is a valid module or bundle version.</returns> | ||
| 143 | public static bool IsValidFourPartVersion(string version) | ||
| 144 | { | ||
| 145 | if (!Common.IsValidBinderVariable(version)) | ||
| 146 | { | ||
| 147 | if (!Version.TryParse(version, out var ver) || 65535 < ver.Major || 65535 < ver.Minor || 65535 < ver.Build || 65535 < ver.Revision) | ||
| 148 | { | ||
| 149 | return false; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | return true; | ||
| 154 | } | ||
| 155 | |||
| 156 | public static bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) | ||
| 157 | { | ||
| 158 | if (String.IsNullOrEmpty(filename)) | ||
| 159 | { | ||
| 160 | return false; | ||
| 161 | } | ||
| 162 | else if (filename.Length > 259) | ||
| 163 | { | ||
| 164 | return false; | ||
| 165 | } | ||
| 166 | |||
| 167 | // Check for a non-period character (all periods is not legal) | ||
| 168 | var allPeriods = true; | ||
| 169 | foreach (var character in filename) | ||
| 170 | { | ||
| 171 | if ('.' != character) | ||
| 172 | { | ||
| 173 | allPeriods = false; | ||
| 174 | break; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | if (allPeriods) | ||
| 179 | { | ||
| 180 | return false; | ||
| 181 | } | ||
| 182 | |||
| 183 | if (allowWildcards) | ||
| 184 | { | ||
| 185 | return filename.IndexOfAny(Common.IllegalWildcardLongFilenameCharacters) == -1; | ||
| 186 | } | ||
| 187 | else if (allowRelative) | ||
| 188 | { | ||
| 189 | return filename.IndexOfAny(Common.IllegalRelativeLongFilenameCharacters) == -1; | ||
| 190 | } | ||
| 191 | else | ||
| 192 | { | ||
| 193 | return filename.IndexOfAny(Common.IllegalLongFilenameCharacters) == -1; | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | public static bool IsValidShortFilename(string filename, bool allowWildcards) | ||
| 198 | { | ||
| 199 | if (String.IsNullOrEmpty(filename)) | ||
| 200 | { | ||
| 201 | return false; | ||
| 202 | } | ||
| 203 | |||
| 204 | if (allowWildcards) | ||
| 205 | { | ||
| 206 | var expectedDot = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters); | ||
| 207 | if (expectedDot == -1) | ||
| 208 | { | ||
| 209 | } | ||
| 210 | else if (filename[expectedDot] != '.') | ||
| 211 | { | ||
| 212 | return false; | ||
| 213 | } | ||
| 214 | else if (expectedDot < filename.Length) | ||
| 215 | { | ||
| 216 | var extensionInvalids = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters, expectedDot + 1); | ||
| 217 | if (extensionInvalids != -1) | ||
| 218 | { | ||
| 219 | return false; | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | var foundPeriod = false; | ||
| 224 | var beforePeriod = 0; | ||
| 225 | var afterPeriod = 0; | ||
| 226 | |||
| 227 | // count the number of characters before and after the period | ||
| 228 | // '*' is not counted because it may represent zero characters | ||
| 229 | foreach (var character in filename) | ||
| 230 | { | ||
| 231 | if ('.' == character) | ||
| 232 | { | ||
| 233 | foundPeriod = true; | ||
| 234 | } | ||
| 235 | else if ('*' != character) | ||
| 236 | { | ||
| 237 | if (foundPeriod) | ||
| 238 | { | ||
| 239 | afterPeriod++; | ||
| 240 | } | ||
| 241 | else | ||
| 242 | { | ||
| 243 | beforePeriod++; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | } | ||
| 247 | |||
| 248 | if (8 >= beforePeriod && 3 >= afterPeriod) | ||
| 249 | { | ||
| 250 | return true; | ||
| 251 | } | ||
| 252 | |||
| 253 | return false; | ||
| 254 | } | ||
| 255 | else | ||
| 256 | { | ||
| 257 | if (filename.Length > 12) | ||
| 258 | { | ||
| 259 | return false; | ||
| 260 | } | ||
| 261 | |||
| 262 | var expectedDot = filename.IndexOfAny(IllegalShortFilenameCharacters); | ||
| 263 | if (expectedDot == -1) | ||
| 264 | { | ||
| 265 | return filename.Length < 9; | ||
| 266 | } | ||
| 267 | else if (expectedDot > 8 || filename[expectedDot] != '.' || expectedDot + 4 < filename.Length) | ||
| 268 | { | ||
| 269 | return false; | ||
| 270 | } | ||
| 271 | |||
| 272 | var validExtension = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1); | ||
| 273 | return validExtension == -1; | ||
| 274 | } | ||
| 275 | } | ||
| 276 | |||
| 277 | /// <summary> | ||
| 278 | /// Generate a new Windows Installer-friendly guid. | ||
| 279 | /// </summary> | ||
| 280 | /// <returns>A new guid.</returns> | ||
| 281 | public static string GenerateGuid() | ||
| 282 | { | ||
| 283 | return Guid.NewGuid().ToString("B").ToUpperInvariant(); | ||
| 284 | } | ||
| 285 | |||
| 286 | /// <summary> | ||
| 287 | /// Generate an identifier by hashing data from the row. | ||
| 288 | /// </summary> | ||
| 289 | /// <param name="prefix">Three letter or less prefix for generated row identifier.</param> | ||
| 290 | /// <param name="args">Information to hash.</param> | ||
| 291 | /// <returns>The generated identifier.</returns> | ||
| 292 | public static string GenerateIdentifier(string prefix, params string[] args) | ||
| 293 | { | ||
| 294 | string base64; | ||
| 295 | |||
| 296 | using (var sha1 = new SHA1CryptoServiceProvider()) | ||
| 297 | { | ||
| 298 | var combined = String.Join("|", args); | ||
| 299 | var data = Encoding.UTF8.GetBytes(combined); | ||
| 300 | var hash = sha1.ComputeHash(data); | ||
| 301 | base64 = Convert.ToBase64String(hash); | ||
| 302 | } | ||
| 303 | |||
| 304 | var identifier = new StringBuilder(32); | ||
| 305 | identifier.Append(prefix); | ||
| 306 | identifier.Append(base64); | ||
| 307 | identifier.Length -= 1; // removes the trailing '=' from base64 | ||
| 308 | identifier.Replace('+', '.'); | ||
| 309 | identifier.Replace('/', '_'); | ||
| 310 | |||
| 311 | return identifier.ToString(); | ||
| 312 | } | ||
| 313 | |||
| 314 | /// <summary> | ||
| 315 | /// Return an identifier based on provided file or directory name | ||
| 316 | /// </summary> | ||
| 317 | /// <param name="name">File/directory name to generate identifer from</param> | ||
| 318 | /// <returns>A version of the name that is a legal identifier.</returns> | ||
| 319 | internal static string GetIdentifierFromName(string name) | ||
| 320 | { | ||
| 321 | StringBuilder sb = null; | ||
| 322 | var offset = 0; | ||
| 323 | |||
| 324 | // MSI identifiers must begin with an alphabetic character or an | ||
| 325 | // underscore. Prefix all other values with an underscore. | ||
| 326 | if (!ValidIdentifierChar(name[0], true)) | ||
| 327 | { | ||
| 328 | sb = new StringBuilder("_" + name); | ||
| 329 | offset = 1; | ||
| 330 | } | ||
| 331 | |||
| 332 | for (var i = 0; i < name.Length; ++i) | ||
| 333 | { | ||
| 334 | if (!ValidIdentifierChar(name[i], false)) | ||
| 335 | { | ||
| 336 | if (sb == null) | ||
| 337 | { | ||
| 338 | sb = new StringBuilder(name); | ||
| 339 | } | ||
| 340 | |||
| 341 | sb[i + offset] = '_'; | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | return sb?.ToString() ?? name; | ||
| 346 | } | ||
| 347 | |||
| 348 | /// <summary> | ||
| 349 | /// Checks if the string contains a property (i.e. "foo[Property]bar") | ||
| 350 | /// </summary> | ||
| 351 | /// <param name="possibleProperty">String to evaluate for properties.</param> | ||
| 352 | /// <returns>True if a property is found in the string.</returns> | ||
| 353 | internal static bool ContainsProperty(string possibleProperty) | ||
| 354 | { | ||
| 355 | var start = possibleProperty.IndexOf('['); | ||
| 356 | if (start != -1 && start < possibleProperty.Length - 2) | ||
| 357 | { | ||
| 358 | var end = possibleProperty.IndexOf(']', start + 1); | ||
| 359 | if (end > start + 1) | ||
| 360 | { | ||
| 361 | // Skip supported property modifiers. | ||
| 362 | if (possibleProperty[start + 1] == '#' || possibleProperty[start + 1] == '$' || possibleProperty[start + 1] == '!') | ||
| 363 | { | ||
| 364 | ++start; | ||
| 365 | } | ||
| 366 | |||
| 367 | var id = possibleProperty.Substring(start + 1, end - 1); | ||
| 368 | |||
| 369 | if (Common.IsIdentifier(id)) | ||
| 370 | { | ||
| 371 | return true; | ||
| 372 | } | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | return false; | ||
| 377 | } | ||
| 378 | |||
| 379 | /// <summary> | ||
| 380 | /// Recursively loops through a directory, changing an attribute on all of the underlying files. | ||
| 381 | /// An example is to add/remove the ReadOnly flag from each file. | ||
| 382 | /// </summary> | ||
| 383 | /// <param name="path">The directory path to start deleting from.</param> | ||
| 384 | /// <param name="fileAttribute">The FileAttribute to change on each file.</param> | ||
| 385 | /// <param name="messageHandler">The message handler.</param> | ||
| 386 | /// <param name="markAttribute">If true, add the attribute to each file. If false, remove it.</param> | ||
| 387 | private static void RecursiveFileAttributes(string path, FileAttributes fileAttribute, bool markAttribute, IMessaging messageHandler) | ||
| 388 | { | ||
| 389 | foreach (var subDirectory in Directory.GetDirectories(path)) | ||
| 390 | { | ||
| 391 | RecursiveFileAttributes(subDirectory, fileAttribute, markAttribute, messageHandler); | ||
| 392 | } | ||
| 393 | |||
| 394 | foreach (var filePath in Directory.GetFiles(path)) | ||
| 395 | { | ||
| 396 | var attributes = File.GetAttributes(filePath); | ||
| 397 | if (markAttribute) | ||
| 398 | { | ||
| 399 | attributes = attributes | fileAttribute; // add to list of attributes | ||
| 400 | } | ||
| 401 | else if (fileAttribute == (attributes & fileAttribute)) // if attribute set | ||
| 402 | { | ||
| 403 | attributes = attributes ^ fileAttribute; // remove from list of attributes | ||
| 404 | } | ||
| 405 | |||
| 406 | try | ||
| 407 | { | ||
| 408 | File.SetAttributes(filePath, attributes); | ||
| 409 | } | ||
| 410 | catch (UnauthorizedAccessException) | ||
| 411 | { | ||
| 412 | messageHandler.Write(WarningMessages.AccessDeniedForSettingAttributes(null, filePath)); | ||
| 413 | } | ||
| 414 | } | ||
| 415 | } | ||
| 416 | |||
| 417 | /// <summary> | ||
| 418 | /// Takes an id, and demodularizes it (if possible). | ||
| 419 | /// </summary> | ||
| 420 | /// <remarks> | ||
| 421 | /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id. | ||
| 422 | /// </remarks> | ||
| 423 | /// <param name="outputType">The type of the output to bind.</param> | ||
| 424 | /// <param name="modularizationGuid">The modularization GUID.</param> | ||
| 425 | /// <param name="id">The id to demodularize.</param> | ||
| 426 | /// <returns>The demodularized id.</returns> | ||
| 427 | public static string Demodularize(OutputType outputType, string modularizationGuid, string id) | ||
| 428 | { | ||
| 429 | if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal)) | ||
| 430 | { | ||
| 431 | id = id.Substring(0, id.Length - 37); | ||
| 432 | } | ||
| 433 | |||
| 434 | return id; | ||
| 435 | } | ||
| 436 | |||
| 437 | /// <summary> | ||
| 438 | /// Get the source/target and short/long file names from an MSI Filename column. | ||
| 439 | /// </summary> | ||
| 440 | /// <param name="value">The Filename value.</param> | ||
| 441 | /// <returns>An array of strings of length 4. The contents are: short target, long target, short source, and long source.</returns> | ||
| 442 | /// <remarks> | ||
| 443 | /// If any particular file name part is not parsed, its set to null in the appropriate location of the returned array of strings. | ||
| 444 | /// Thus the returned array will always be of length 4. | ||
| 445 | /// </remarks> | ||
| 446 | public static string[] GetNames(string value) | ||
| 447 | { | ||
| 448 | var targetSeparator = value.IndexOf(':'); | ||
| 449 | |||
| 450 | // split source and target | ||
| 451 | string sourceName = null; | ||
| 452 | var targetName = value; | ||
| 453 | if (0 <= targetSeparator) | ||
| 454 | { | ||
| 455 | sourceName = value.Substring(targetSeparator + 1); | ||
| 456 | targetName = value.Substring(0, targetSeparator); | ||
| 457 | } | ||
| 458 | |||
| 459 | // split the source short and long names | ||
| 460 | string sourceLongName = null; | ||
| 461 | if (null != sourceName) | ||
| 462 | { | ||
| 463 | var sourceLongNameSeparator = sourceName.IndexOf('|'); | ||
| 464 | if (0 <= sourceLongNameSeparator) | ||
| 465 | { | ||
| 466 | sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); | ||
| 467 | sourceName = sourceName.Substring(0, sourceLongNameSeparator); | ||
| 468 | } | ||
| 469 | } | ||
| 470 | |||
| 471 | // split the target short and long names | ||
| 472 | var targetLongNameSeparator = targetName.IndexOf('|'); | ||
| 473 | string targetLongName = null; | ||
| 474 | if (0 <= targetLongNameSeparator) | ||
| 475 | { | ||
| 476 | targetLongName = targetName.Substring(targetLongNameSeparator + 1); | ||
| 477 | targetName = targetName.Substring(0, targetLongNameSeparator); | ||
| 478 | } | ||
| 479 | |||
| 480 | // Remove the long source name when its identical to the short source name. | ||
| 481 | if (null != sourceName && sourceName == sourceLongName) | ||
| 482 | { | ||
| 483 | sourceLongName = null; | ||
| 484 | } | ||
| 485 | |||
| 486 | // Remove the long target name when its identical to the long target name. | ||
| 487 | if (null != targetName && targetName == targetLongName) | ||
| 488 | { | ||
| 489 | targetLongName = null; | ||
| 490 | } | ||
| 491 | |||
| 492 | // Remove the source names when they are identical to the target names. | ||
| 493 | if (sourceName == targetName && sourceLongName == targetLongName) | ||
| 494 | { | ||
| 495 | sourceName = null; | ||
| 496 | sourceLongName = null; | ||
| 497 | } | ||
| 498 | |||
| 499 | // target name(s) | ||
| 500 | if ("." == targetName) | ||
| 501 | { | ||
| 502 | targetName = null; | ||
| 503 | } | ||
| 504 | |||
| 505 | if ("." == targetLongName) | ||
| 506 | { | ||
| 507 | targetLongName = null; | ||
| 508 | } | ||
| 509 | |||
| 510 | // source name(s) | ||
| 511 | if ("." == sourceName) | ||
| 512 | { | ||
| 513 | sourceName = null; | ||
| 514 | } | ||
| 515 | |||
| 516 | if ("." == sourceLongName) | ||
| 517 | { | ||
| 518 | sourceLongName = null; | ||
| 519 | } | ||
| 520 | |||
| 521 | return new[] { targetName, targetLongName, sourceName, sourceLongName }; | ||
| 522 | } | ||
| 523 | |||
| 524 | /// <summary> | ||
| 525 | /// Get a source/target and short/long file name from an MSI Filename column. | ||
| 526 | /// </summary> | ||
| 527 | /// <param name="value">The Filename value.</param> | ||
| 528 | /// <param name="source">true to get a source name; false to get a target name</param> | ||
| 529 | /// <param name="longName">true to get a long name; false to get a short name</param> | ||
| 530 | /// <returns>The name.</returns> | ||
| 531 | public static string GetName(string value, bool source, bool longName) | ||
| 532 | { | ||
| 533 | var names = GetNames(value); | ||
| 534 | |||
| 535 | if (source) | ||
| 536 | { | ||
| 537 | if (longName && null != names[3]) | ||
| 538 | { | ||
| 539 | return names[3]; | ||
| 540 | } | ||
| 541 | else if (null != names[2]) | ||
| 542 | { | ||
| 543 | return names[2]; | ||
| 544 | } | ||
| 545 | } | ||
| 546 | |||
| 547 | if (longName && null != names[1]) | ||
| 548 | { | ||
| 549 | return names[1]; | ||
| 550 | } | ||
| 551 | else | ||
| 552 | { | ||
| 553 | return names[0]; | ||
| 554 | } | ||
| 555 | } | ||
| 556 | |||
| 557 | /// <summary> | ||
| 558 | /// Get an attribute value. | ||
| 559 | /// </summary> | ||
| 560 | /// <param name="messaging"></param> | ||
| 561 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 562 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 563 | /// <param name="emptyRule">A rule for the contents of the value. If the contents do not follow the rule, an error is thrown.</param> | ||
| 564 | /// <returns>The attribute's value.</returns> | ||
| 565 | internal static string GetAttributeValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule) | ||
| 566 | { | ||
| 567 | var value = attribute.Value; | ||
| 568 | |||
| 569 | if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) || | ||
| 570 | (emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value))) | ||
| 571 | { | ||
| 572 | messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); | ||
| 573 | return String.Empty; | ||
| 574 | } | ||
| 575 | |||
| 576 | return value; | ||
| 577 | } | ||
| 578 | |||
| 579 | /// <summary> | ||
| 580 | /// Verifies that a value is a legal identifier. | ||
| 581 | /// </summary> | ||
| 582 | /// <param name="value">The value to verify.</param> | ||
| 583 | /// <returns>true if the value is an identifier; false otherwise.</returns> | ||
| 584 | public static bool IsIdentifier(string value) | ||
| 585 | { | ||
| 586 | if (String.IsNullOrEmpty(value)) | ||
| 587 | { | ||
| 588 | return false; | ||
| 589 | } | ||
| 590 | |||
| 591 | for (var i = 0; i < value.Length; ++i) | ||
| 592 | { | ||
| 593 | if (!ValidIdentifierChar(value[i], i == 0)) | ||
| 594 | { | ||
| 595 | return false; | ||
| 596 | } | ||
| 597 | } | ||
| 598 | |||
| 599 | return true; | ||
| 600 | } | ||
| 601 | |||
| 602 | /// <summary> | ||
| 603 | /// Get an identifier attribute value and displays an error for an illegal identifier value. | ||
| 604 | /// </summary> | ||
| 605 | /// <param name="messaging"></param> | ||
| 606 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 607 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 608 | /// <returns>The attribute's identifier value or a special value if an error occurred.</returns> | ||
| 609 | internal static string GetAttributeIdentifierValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 610 | { | ||
| 611 | var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); | ||
| 612 | |||
| 613 | if (Common.IsIdentifier(value)) | ||
| 614 | { | ||
| 615 | if (72 < value.Length) | ||
| 616 | { | ||
| 617 | messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 618 | } | ||
| 619 | |||
| 620 | return value; | ||
| 621 | } | ||
| 622 | else | ||
| 623 | { | ||
| 624 | if (value.StartsWith("[", StringComparison.Ordinal) && value.EndsWith("]", StringComparison.Ordinal)) | ||
| 625 | { | ||
| 626 | messaging.Write(ErrorMessages.IllegalIdentifierLooksLikeFormatted(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 627 | } | ||
| 628 | else | ||
| 629 | { | ||
| 630 | messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 631 | } | ||
| 632 | |||
| 633 | return String.Empty; | ||
| 634 | } | ||
| 635 | } | ||
| 636 | |||
| 637 | /// <summary> | ||
| 638 | /// Get an integer attribute value and displays an error for an illegal integer value. | ||
| 639 | /// </summary> | ||
| 640 | /// <param name="messaging"></param> | ||
| 641 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 642 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 643 | /// <param name="minimum">The minimum legal value.</param> | ||
| 644 | /// <param name="maximum">The maximum legal value.</param> | ||
| 645 | /// <returns>The attribute's integer value or a special value if an error occurred during conversion.</returns> | ||
| 646 | public static int GetAttributeIntegerValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) | ||
| 647 | { | ||
| 648 | Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); | ||
| 649 | |||
| 650 | var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); | ||
| 651 | var integer = CompilerConstants.IllegalInteger; | ||
| 652 | |||
| 653 | if (0 < value.Length) | ||
| 654 | { | ||
| 655 | if (Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out integer)) | ||
| 656 | { | ||
| 657 | if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) | ||
| 658 | { | ||
| 659 | messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); | ||
| 660 | } | ||
| 661 | else if (minimum > integer || maximum < integer) | ||
| 662 | { | ||
| 663 | messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); | ||
| 664 | integer = CompilerConstants.IllegalInteger; | ||
| 665 | } | ||
| 666 | } | ||
| 667 | else | ||
| 668 | { | ||
| 669 | messaging.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 670 | } | ||
| 671 | } | ||
| 672 | |||
| 673 | return integer; | ||
| 674 | } | ||
| 675 | |||
| 676 | /// <summary> | ||
| 677 | /// Gets a yes/no value and displays an error for an illegal yes/no value. | ||
| 678 | /// </summary> | ||
| 679 | /// <param name="messaging"></param> | ||
| 680 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 681 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 682 | /// <returns>The attribute's YesNoType value.</returns> | ||
| 683 | internal static YesNoType GetAttributeYesNoValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 684 | { | ||
| 685 | var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); | ||
| 686 | var yesNo = YesNoType.IllegalValue; | ||
| 687 | |||
| 688 | if ("yes".Equals(value) || "true".Equals(value)) | ||
| 689 | { | ||
| 690 | yesNo = YesNoType.Yes; | ||
| 691 | } | ||
| 692 | else if ("no".Equals(value) || "false".Equals(value)) | ||
| 693 | { | ||
| 694 | yesNo = YesNoType.No; | ||
| 695 | } | ||
| 696 | else | ||
| 697 | { | ||
| 698 | messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 699 | } | ||
| 700 | |||
| 701 | return yesNo; | ||
| 702 | } | ||
| 703 | |||
| 704 | /// <summary> | ||
| 705 | /// Gets the text of an XElement. | ||
| 706 | /// </summary> | ||
| 707 | /// <param name="node">Element to get text.</param> | ||
| 708 | /// <returns>The element's text.</returns> | ||
| 709 | internal static string GetInnerText(XElement node) | ||
| 710 | { | ||
| 711 | var text = node.Nodes().Where(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType).Cast<XText>().FirstOrDefault(); | ||
| 712 | return text?.Value; | ||
| 713 | } | ||
| 714 | |||
| 715 | internal static bool TryParseWixVariable(string value, int start, out ParsedWixVariable parsedVariable) | ||
| 716 | { | ||
| 717 | parsedVariable = null; | ||
| 718 | |||
| 719 | if (String.IsNullOrEmpty(value) || start >= value.Length) | ||
| 720 | { | ||
| 721 | return false; | ||
| 722 | } | ||
| 723 | |||
| 724 | var startWixVariable = value.IndexOf("!(", start, StringComparison.Ordinal); | ||
| 725 | if (startWixVariable == -1) | ||
| 726 | { | ||
| 727 | return false; | ||
| 728 | } | ||
| 729 | |||
| 730 | var firstDot = value.IndexOf('.', startWixVariable + 1); | ||
| 731 | if (firstDot == -1) | ||
| 732 | { | ||
| 733 | return false; | ||
| 734 | } | ||
| 735 | |||
| 736 | var ns = value.Substring(startWixVariable + 2, firstDot - startWixVariable - 2); | ||
| 737 | if (ns != "loc" && ns != "bind" && ns != "wix") | ||
| 738 | { | ||
| 739 | return false; | ||
| 740 | } | ||
| 741 | |||
| 742 | var closeParen = value.IndexOf(')', firstDot); | ||
| 743 | if (closeParen == -1) | ||
| 744 | { | ||
| 745 | return false; | ||
| 746 | } | ||
| 747 | |||
| 748 | string name; | ||
| 749 | string scope = null; | ||
| 750 | string defaultValue = null; | ||
| 751 | |||
| 752 | var equalsDefaultValue = value.IndexOf('=', firstDot + 1, closeParen - firstDot); | ||
| 753 | var end = equalsDefaultValue == -1 ? closeParen : equalsDefaultValue; | ||
| 754 | var secondDot = value.IndexOf('.', firstDot + 1, end - firstDot); | ||
| 755 | |||
| 756 | if (secondDot == -1) | ||
| 757 | { | ||
| 758 | name = value.Substring(firstDot + 1, end - firstDot - 1); | ||
| 759 | } | ||
| 760 | else | ||
| 761 | { | ||
| 762 | name = value.Substring(firstDot + 1, secondDot - firstDot - 1); | ||
| 763 | scope = value.Substring(secondDot + 1, end - secondDot - 1); | ||
| 764 | |||
| 765 | if (!Common.IsIdentifier(scope)) | ||
| 766 | { | ||
| 767 | return false; | ||
| 768 | } | ||
| 769 | } | ||
| 770 | |||
| 771 | if (!Common.IsIdentifier(name)) | ||
| 772 | { | ||
| 773 | return false; | ||
| 774 | } | ||
| 775 | |||
| 776 | if (equalsDefaultValue != -1 && equalsDefaultValue < closeParen) | ||
| 777 | { | ||
| 778 | defaultValue = value.Substring(equalsDefaultValue + 1, closeParen - equalsDefaultValue - 1); | ||
| 779 | } | ||
| 780 | |||
| 781 | parsedVariable = new ParsedWixVariable | ||
| 782 | { | ||
| 783 | Index = startWixVariable, | ||
| 784 | Length = closeParen - startWixVariable + 1, | ||
| 785 | Namespace = ns, | ||
| 786 | Name = name, | ||
| 787 | Scope = scope, | ||
| 788 | DefaultValue = defaultValue | ||
| 789 | }; | ||
| 790 | |||
| 791 | return true; | ||
| 792 | } | ||
| 793 | |||
| 794 | /// <summary> | ||
| 795 | /// Display an unexpected attribute error. | ||
| 796 | /// </summary> | ||
| 797 | /// <param name="messaging"></param> | ||
| 798 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 799 | /// <param name="attribute">The attribute.</param> | ||
| 800 | public static void UnexpectedAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 801 | { | ||
| 802 | // Ignore elements defined by the W3C because we'll assume they are always right. | ||
| 803 | if (!((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || | ||
| 804 | attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) | ||
| 805 | { | ||
| 806 | messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); | ||
| 807 | } | ||
| 808 | } | ||
| 809 | |||
| 810 | /// <summary> | ||
| 811 | /// Display an unsupported extension attribute error. | ||
| 812 | /// </summary> | ||
| 813 | /// <param name="messaging"></param> | ||
| 814 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 815 | /// <param name="extensionAttribute">The extension attribute.</param> | ||
| 816 | internal static void UnsupportedExtensionAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute extensionAttribute) | ||
| 817 | { | ||
| 818 | // Ignore elements defined by the W3C because we'll assume they are always right. | ||
| 819 | if (!((String.IsNullOrEmpty(extensionAttribute.Name.NamespaceName) && extensionAttribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || | ||
| 820 | extensionAttribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) | ||
| 821 | { | ||
| 822 | messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName)); | ||
| 823 | } | ||
| 824 | } | ||
| 825 | |||
| 826 | private static bool ValidIdentifierChar(char c, bool firstChar) | ||
| 827 | { | ||
| 828 | return ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || '_' == c || | ||
| 829 | (!firstChar && (Char.IsDigit(c) || '.' == c)); | ||
| 830 | } | ||
| 831 | } | ||
| 832 | } | ||
diff --git a/src/wix/WixToolset.Core/Compile/CompilerPayload.cs b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs new file mode 100644 index 00000000..3f423034 --- /dev/null +++ b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs | |||
| @@ -0,0 +1,291 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Xml.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Burn; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | |||
| 12 | internal class CompilerPayload | ||
| 13 | { | ||
| 14 | public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default; | ||
| 15 | |||
| 16 | public string Description { get; set; } | ||
| 17 | |||
| 18 | public string DownloadUrl { get; set; } | ||
| 19 | |||
| 20 | public string Hash { get; set; } | ||
| 21 | |||
| 22 | public Identifier Id { get; set; } | ||
| 23 | |||
| 24 | public bool IsRemoteAllowed { get; set; } | ||
| 25 | |||
| 26 | public bool IsRequired { get; set; } = true; | ||
| 27 | |||
| 28 | public string Name { get; set; } | ||
| 29 | |||
| 30 | public string ProductName { get; set; } | ||
| 31 | |||
| 32 | public long? Size { get; set; } | ||
| 33 | |||
| 34 | public string SourceFile { get; set; } | ||
| 35 | |||
| 36 | public string Version { get; set; } | ||
| 37 | |||
| 38 | public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element) | ||
| 39 | { | ||
| 40 | this.Core = core; | ||
| 41 | this.Element = element; | ||
| 42 | this.SourceLineNumbers = sourceLineNumbers; | ||
| 43 | } | ||
| 44 | |||
| 45 | private CompilerCore Core { get; } | ||
| 46 | |||
| 47 | private XElement Element { get; } | ||
| 48 | |||
| 49 | private SourceLineNumber SourceLineNumbers { get; } | ||
| 50 | |||
| 51 | private void CalculateAndVerifyFields() | ||
| 52 | { | ||
| 53 | var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash); | ||
| 54 | |||
| 55 | if (String.IsNullOrEmpty(this.SourceFile)) | ||
| 56 | { | ||
| 57 | if (!String.IsNullOrEmpty(this.Name) && !isRemote) | ||
| 58 | { | ||
| 59 | this.SourceFile = Path.Combine("SourceDir", this.Name); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | else if (this.SourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) | ||
| 63 | { | ||
| 64 | if (String.IsNullOrEmpty(this.Name)) | ||
| 65 | { | ||
| 66 | this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile", this.SourceFile)); | ||
| 67 | } | ||
| 68 | else | ||
| 69 | { | ||
| 70 | this.SourceFile = Path.Combine(this.SourceFile, Path.GetFileName(this.Name)); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | if (String.IsNullOrEmpty(this.SourceFile) && !isRemote) | ||
| 75 | { | ||
| 76 | if (this.IsRequired) | ||
| 77 | { | ||
| 78 | if (!this.IsRemoteAllowed) | ||
| 79 | { | ||
| 80 | this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile")); | ||
| 81 | } | ||
| 82 | else | ||
| 83 | { | ||
| 84 | this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash")); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
| 88 | else if (this.IsRemoteAllowed) | ||
| 89 | { | ||
| 90 | var isLocal = !String.IsNullOrEmpty(this.SourceFile); | ||
| 91 | |||
| 92 | if (isLocal) | ||
| 93 | { | ||
| 94 | if (isRemote) | ||
| 95 | { | ||
| 96 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile")); | ||
| 97 | } | ||
| 98 | |||
| 99 | if (!String.IsNullOrEmpty(this.Description)) | ||
| 100 | { | ||
| 101 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Description", "SourceFile")); | ||
| 102 | } | ||
| 103 | |||
| 104 | if (!String.IsNullOrEmpty(this.ProductName)) | ||
| 105 | { | ||
| 106 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "ProductName", "SourceFile")); | ||
| 107 | } | ||
| 108 | |||
| 109 | if (this.Size.HasValue) | ||
| 110 | { | ||
| 111 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "SourceFile")); | ||
| 112 | } | ||
| 113 | |||
| 114 | if (!String.IsNullOrEmpty(this.Version)) | ||
| 115 | { | ||
| 116 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Version", "SourceFile")); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | else | ||
| 120 | { | ||
| 121 | if (String.IsNullOrEmpty(this.DownloadUrl)) | ||
| 122 | { | ||
| 123 | this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash")); | ||
| 124 | } | ||
| 125 | |||
| 126 | if (String.IsNullOrEmpty(this.Name)) | ||
| 127 | { | ||
| 128 | this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash")); | ||
| 129 | } | ||
| 130 | |||
| 131 | if (!this.Size.HasValue) | ||
| 132 | { | ||
| 133 | this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "Hash")); | ||
| 134 | } | ||
| 135 | |||
| 136 | if (YesNoDefaultType.Yes == this.Compressed) | ||
| 137 | { | ||
| 138 | this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName)); | ||
| 139 | } | ||
| 140 | |||
| 141 | this.Compressed = YesNoDefaultType.No; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | public WixBundlePayloadSymbol CreatePayloadSymbol(ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown, string previousId = null) | ||
| 147 | { | ||
| 148 | WixBundlePayloadSymbol symbol = null; | ||
| 149 | |||
| 150 | if (parentType == ComplexReferenceParentType.Container && parentId == BurnConstants.BurnUXContainerName) | ||
| 151 | { | ||
| 152 | if (this.Compressed == YesNoDefaultType.No) | ||
| 153 | { | ||
| 154 | this.Core.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(this.SourceLineNumbers, this.SourceFile)); | ||
| 155 | } | ||
| 156 | |||
| 157 | if (!String.IsNullOrEmpty(this.DownloadUrl)) | ||
| 158 | { | ||
| 159 | this.Core.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(this.SourceLineNumbers, this.Id.Id)); | ||
| 160 | } | ||
| 161 | |||
| 162 | this.Compressed = YesNoDefaultType.Yes; | ||
| 163 | this.DownloadUrl = null; | ||
| 164 | } | ||
| 165 | |||
| 166 | if (!this.Core.EncounteredError) | ||
| 167 | { | ||
| 168 | symbol = this.Core.AddSymbol(new WixBundlePayloadSymbol(this.SourceLineNumbers, this.Id) | ||
| 169 | { | ||
| 170 | Name = String.IsNullOrEmpty(this.Name) ? Path.GetFileName(this.SourceFile) : this.Name, | ||
| 171 | SourceFile = new IntermediateFieldPathValue { Path = this.SourceFile }, | ||
| 172 | DownloadUrl = this.DownloadUrl, | ||
| 173 | Compressed = (this.Compressed == YesNoDefaultType.Yes) ? true : (this.Compressed == YesNoDefaultType.No) ? (bool?)false : null, | ||
| 174 | UnresolvedSourceFile = this.SourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. | ||
| 175 | DisplayName = this.ProductName, | ||
| 176 | Description = this.Description, | ||
| 177 | Hash = this.Hash, | ||
| 178 | FileSize = this.Size, | ||
| 179 | Version = this.Version, | ||
| 180 | }); | ||
| 181 | |||
| 182 | this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, previousType, previousId); | ||
| 183 | } | ||
| 184 | |||
| 185 | return symbol; | ||
| 186 | } | ||
| 187 | |||
| 188 | public void FinishCompilingPackage() | ||
| 189 | { | ||
| 190 | this.CalculateAndVerifyFields(); | ||
| 191 | this.GenerateIdFromFilename(); | ||
| 192 | |||
| 193 | if (this.Id == null) | ||
| 194 | { | ||
| 195 | this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Id")); | ||
| 196 | this.Id = Identifier.Invalid; | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | public void FinishCompilingPackagePayload() | ||
| 201 | { | ||
| 202 | this.CalculateAndVerifyFields(); | ||
| 203 | this.GenerateIdFromFilename(); | ||
| 204 | this.GenerateIdFromPrefix("ppy"); | ||
| 205 | } | ||
| 206 | |||
| 207 | public void FinishCompilingPayload() | ||
| 208 | { | ||
| 209 | this.CalculateAndVerifyFields(); | ||
| 210 | this.GenerateIdFromPrefix("pay"); | ||
| 211 | } | ||
| 212 | |||
| 213 | private void GenerateIdFromFilename() | ||
| 214 | { | ||
| 215 | if (this.Id == null) | ||
| 216 | { | ||
| 217 | if (!String.IsNullOrEmpty(this.Name)) | ||
| 218 | { | ||
| 219 | this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.Name)); | ||
| 220 | } | ||
| 221 | else if (!String.IsNullOrEmpty(this.SourceFile)) | ||
| 222 | { | ||
| 223 | this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.SourceFile)); | ||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | private void GenerateIdFromPrefix(string prefix) | ||
| 229 | { | ||
| 230 | if (this.Id == null) | ||
| 231 | { | ||
| 232 | this.Id = this.Core.CreateIdentifier(prefix, this.SourceFile?.ToUpperInvariant() ?? String.Empty); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | public void ParseCompressed(XAttribute attrib) | ||
| 237 | { | ||
| 238 | this.Compressed = this.Core.GetAttributeYesNoDefaultValue(this.SourceLineNumbers, attrib); | ||
| 239 | } | ||
| 240 | |||
| 241 | public void ParseDescription(XAttribute attrib) | ||
| 242 | { | ||
| 243 | this.Description = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); | ||
| 244 | } | ||
| 245 | |||
| 246 | public void ParseDownloadUrl(XAttribute attrib) | ||
| 247 | { | ||
| 248 | this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); | ||
| 249 | } | ||
| 250 | |||
| 251 | public void ParseHash(XAttribute attrib) | ||
| 252 | { | ||
| 253 | this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); | ||
| 254 | } | ||
| 255 | |||
| 256 | public void ParseId(XAttribute attrib) | ||
| 257 | { | ||
| 258 | this.Id = this.Core.GetAttributeIdentifier(this.SourceLineNumbers, attrib); | ||
| 259 | } | ||
| 260 | |||
| 261 | public void ParseName(XAttribute attrib) | ||
| 262 | { | ||
| 263 | this.Name = this.Core.GetAttributeLongFilename(this.SourceLineNumbers, attrib, false, true); | ||
| 264 | if (!this.Core.IsValidLongFilename(this.Name, false, true)) | ||
| 265 | { | ||
| 266 | this.Core.Write(ErrorMessages.IllegalLongFilename(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", this.Name)); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | public void ParseProductName(XAttribute attrib) | ||
| 271 | { | ||
| 272 | this.ProductName = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); | ||
| 273 | } | ||
| 274 | |||
| 275 | public void ParseSize(XAttribute attrib) | ||
| 276 | { | ||
| 277 | this.Size = this.Core.GetAttributeLongValue(this.SourceLineNumbers, attrib, 1, Int64.MaxValue); | ||
| 278 | } | ||
| 279 | |||
| 280 | public void ParseSourceFile(XAttribute attrib) | ||
| 281 | { | ||
| 282 | this.SourceFile = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); | ||
| 283 | } | ||
| 284 | |||
| 285 | public void ParseVersion(XAttribute attrib) | ||
| 286 | { | ||
| 287 | this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); | ||
| 288 | } | ||
| 289 | |||
| 290 | } | ||
| 291 | } | ||
diff --git a/src/wix/WixToolset.Core/CompileContext.cs b/src/wix/WixToolset.Core/CompileContext.cs new file mode 100644 index 00000000..d84d7aac --- /dev/null +++ b/src/wix/WixToolset.Core/CompileContext.cs | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Threading; | ||
| 8 | using System.Xml.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | |||
| 13 | internal class CompileContext : ICompileContext | ||
| 14 | { | ||
| 15 | internal CompileContext(IServiceProvider serviceProvider) | ||
| 16 | { | ||
| 17 | this.ServiceProvider = serviceProvider; | ||
| 18 | } | ||
| 19 | |||
| 20 | public IServiceProvider ServiceProvider { get; } | ||
| 21 | |||
| 22 | public string CompilationId { get; set; } | ||
| 23 | |||
| 24 | public IReadOnlyCollection<ICompilerExtension> Extensions { get; set; } | ||
| 25 | |||
| 26 | public Platform Platform { get; set; } | ||
| 27 | |||
| 28 | public bool IsCurrentPlatform64Bit => this.Platform == Platform.ARM64 || this.Platform == Platform.X64; | ||
| 29 | |||
| 30 | public XDocument Source { get; set; } | ||
| 31 | |||
| 32 | public CancellationToken CancellationToken { get; set; } | ||
| 33 | } | ||
| 34 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs new file mode 100644 index 00000000..c39bec70 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler.cs | |||
| @@ -0,0 +1,8514 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using System.Text; | ||
| 12 | using System.Xml.Linq; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Symbols; | ||
| 15 | using WixToolset.Data.WindowsInstaller; | ||
| 16 | using WixToolset.Extensibility; | ||
| 17 | using WixToolset.Extensibility.Data; | ||
| 18 | using WixToolset.Extensibility.Services; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Compiler of the WiX toolset. | ||
| 22 | /// </summary> | ||
| 23 | internal partial class Compiler : ICompiler | ||
| 24 | { | ||
| 25 | private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB | ||
| 26 | private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) | ||
| 27 | |||
| 28 | private const char ComponentIdPlaceholderStart = (char)167; | ||
| 29 | private const char ComponentIdPlaceholderEnd = (char)167; | ||
| 30 | private Dictionary<string, string> componentIdPlaceholders; | ||
| 31 | |||
| 32 | // If these are true you know you are building a module or product | ||
| 33 | // but if they are false you cannot not be sure they will not end | ||
| 34 | // up a product or module. Use these flags carefully. | ||
| 35 | private bool compilingModule; | ||
| 36 | private bool compilingProduct; | ||
| 37 | |||
| 38 | private string activeName; | ||
| 39 | private string activeLanguage; | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Type of RadioButton element in a group. | ||
| 43 | /// </summary> | ||
| 44 | private enum RadioButtonType | ||
| 45 | { | ||
| 46 | /// <summary>Not set, yet.</summary> | ||
| 47 | NotSet, | ||
| 48 | |||
| 49 | /// <summary>Text</summary> | ||
| 50 | Text, | ||
| 51 | |||
| 52 | /// <summary>Bitmap</summary> | ||
| 53 | Bitmap, | ||
| 54 | |||
| 55 | /// <summary>Icon</summary> | ||
| 56 | Icon, | ||
| 57 | } | ||
| 58 | |||
| 59 | internal Compiler(IServiceProvider serviceProvider) | ||
| 60 | { | ||
| 61 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 62 | } | ||
| 63 | |||
| 64 | public IMessaging Messaging { get; } | ||
| 65 | |||
| 66 | private ICompileContext Context { get; set; } | ||
| 67 | |||
| 68 | private CompilerCore Core { get; set; } | ||
| 69 | |||
| 70 | /// <summary> | ||
| 71 | /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. | ||
| 72 | /// </summary> | ||
| 73 | /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value> | ||
| 74 | public Platform CurrentPlatform => this.Context.Platform; | ||
| 75 | |||
| 76 | /// <summary> | ||
| 77 | /// Gets or sets the option to show pedantic messages. | ||
| 78 | /// </summary> | ||
| 79 | /// <value>The option to show pedantic messages.</value> | ||
| 80 | public bool ShowPedanticMessages { get; set; } | ||
| 81 | |||
| 82 | /// <summary> | ||
| 83 | /// Compiles the provided Xml document into an intermediate object | ||
| 84 | /// </summary> | ||
| 85 | /// <returns>Intermediate object representing compiled source document.</returns> | ||
| 86 | /// <remarks>This method is not thread-safe.</remarks> | ||
| 87 | public Intermediate Compile(ICompileContext context) | ||
| 88 | { | ||
| 89 | var target = new Intermediate(); | ||
| 90 | |||
| 91 | if (String.IsNullOrEmpty(context.CompilationId)) | ||
| 92 | { | ||
| 93 | context.CompilationId = target.Id; | ||
| 94 | } | ||
| 95 | |||
| 96 | this.Context = context; | ||
| 97 | |||
| 98 | var extensionsByNamespace = new Dictionary<XNamespace, ICompilerExtension>(); | ||
| 99 | |||
| 100 | foreach (var extension in this.Context.Extensions) | ||
| 101 | { | ||
| 102 | if (!extensionsByNamespace.TryGetValue(extension.Namespace, out var collidingExtension)) | ||
| 103 | { | ||
| 104 | extensionsByNamespace.Add(extension.Namespace, extension); | ||
| 105 | } | ||
| 106 | else | ||
| 107 | { | ||
| 108 | this.Messaging.Write(ErrorMessages.DuplicateExtensionXmlSchemaNamespace(extension.GetType().ToString(), extension.Namespace.NamespaceName, collidingExtension.GetType().ToString())); | ||
| 109 | } | ||
| 110 | |||
| 111 | extension.PreCompile(this.Context); | ||
| 112 | } | ||
| 113 | |||
| 114 | // Try to compile it. | ||
| 115 | try | ||
| 116 | { | ||
| 117 | var parseHelper = this.Context.ServiceProvider.GetService<IParseHelper>(); | ||
| 118 | |||
| 119 | this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace); | ||
| 120 | this.Core.ShowPedanticMessages = this.ShowPedanticMessages; | ||
| 121 | this.componentIdPlaceholders = new Dictionary<string, string>(); | ||
| 122 | |||
| 123 | // parse the document | ||
| 124 | var source = this.Context.Source; | ||
| 125 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(source.Root); | ||
| 126 | if ("Wix" == source.Root.Name.LocalName) | ||
| 127 | { | ||
| 128 | if (CompilerCore.WixNamespace == source.Root.Name.Namespace) | ||
| 129 | { | ||
| 130 | this.ParseWixElement(source.Root); | ||
| 131 | } | ||
| 132 | else // invalid or missing namespace | ||
| 133 | { | ||
| 134 | if (String.IsNullOrEmpty(source.Root.Name.NamespaceName)) | ||
| 135 | { | ||
| 136 | this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", CompilerCore.WixNamespace.ToString())); | ||
| 137 | } | ||
| 138 | else | ||
| 139 | { | ||
| 140 | this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", source.Root.Name.NamespaceName, CompilerCore.WixNamespace.ToString())); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | } | ||
| 144 | else | ||
| 145 | { | ||
| 146 | this.Core.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, source.Root.Name.LocalName, "source", "Wix")); | ||
| 147 | } | ||
| 148 | |||
| 149 | // Resolve any Component Id placeholders compiled into the intermediate. | ||
| 150 | this.ResolveComponentIdPlaceholders(target); | ||
| 151 | } | ||
| 152 | finally | ||
| 153 | { | ||
| 154 | foreach (var extension in this.Context.Extensions) | ||
| 155 | { | ||
| 156 | extension.PostCompile(target); | ||
| 157 | } | ||
| 158 | |||
| 159 | this.Core = null; | ||
| 160 | } | ||
| 161 | |||
| 162 | target.UpdateLevel(Data.IntermediateLevels.Compiled); | ||
| 163 | |||
| 164 | return this.Messaging.EncounteredError ? null : target; | ||
| 165 | } | ||
| 166 | |||
| 167 | /// <summary> | ||
| 168 | /// Parses a Wix element. | ||
| 169 | /// </summary> | ||
| 170 | /// <param name="node">Element to parse.</param> | ||
| 171 | private void ParseWixElement(XElement node) | ||
| 172 | { | ||
| 173 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 174 | string requiredVersion = null; | ||
| 175 | |||
| 176 | foreach (var attrib in node.Attributes()) | ||
| 177 | { | ||
| 178 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 179 | { | ||
| 180 | switch (attrib.Name.LocalName) | ||
| 181 | { | ||
| 182 | case "RequiredVersion": | ||
| 183 | requiredVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 184 | break; | ||
| 185 | default: | ||
| 186 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 187 | break; | ||
| 188 | } | ||
| 189 | } | ||
| 190 | else | ||
| 191 | { | ||
| 192 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | if (null != requiredVersion) | ||
| 197 | { | ||
| 198 | this.Core.VerifyRequiredVersion(sourceLineNumbers, requiredVersion); | ||
| 199 | } | ||
| 200 | |||
| 201 | foreach (var child in node.Elements()) | ||
| 202 | { | ||
| 203 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 204 | { | ||
| 205 | switch (child.Name.LocalName) | ||
| 206 | { | ||
| 207 | case "Bundle": | ||
| 208 | this.ParseBundleElement(child); | ||
| 209 | break; | ||
| 210 | case "Fragment": | ||
| 211 | this.ParseFragmentElement(child); | ||
| 212 | break; | ||
| 213 | case "Module": | ||
| 214 | this.ParseModuleElement(child); | ||
| 215 | break; | ||
| 216 | case "PatchCreation": | ||
| 217 | this.ParsePatchCreationElement(child); | ||
| 218 | break; | ||
| 219 | case "Package": | ||
| 220 | this.ParsePackageElement(child); | ||
| 221 | break; | ||
| 222 | case "Patch": | ||
| 223 | this.ParsePatchElement(child); | ||
| 224 | break; | ||
| 225 | default: | ||
| 226 | this.Core.UnexpectedElement(node, child); | ||
| 227 | break; | ||
| 228 | } | ||
| 229 | } | ||
| 230 | else | ||
| 231 | { | ||
| 232 | this.Core.ParseExtensionElement(node, child); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | private void ResolveComponentIdPlaceholders(Intermediate target) | ||
| 238 | { | ||
| 239 | if (0 < this.componentIdPlaceholders.Count) | ||
| 240 | { | ||
| 241 | foreach (var section in target.Sections) | ||
| 242 | { | ||
| 243 | foreach (var symbol in section.Symbols) | ||
| 244 | { | ||
| 245 | foreach (var field in symbol.Fields) | ||
| 246 | { | ||
| 247 | if (field != null && field.Type == IntermediateFieldType.String) | ||
| 248 | { | ||
| 249 | var data = field.AsString(); | ||
| 250 | if (!String.IsNullOrEmpty(data)) | ||
| 251 | { | ||
| 252 | var changed = false; | ||
| 253 | var start = data.IndexOf(ComponentIdPlaceholderStart); | ||
| 254 | while (start != -1) | ||
| 255 | { | ||
| 256 | var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1); | ||
| 257 | if (end == -1) | ||
| 258 | { | ||
| 259 | break; | ||
| 260 | } | ||
| 261 | |||
| 262 | var placeholderId = data.Substring(start, end - start + 1); | ||
| 263 | if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value)) | ||
| 264 | { | ||
| 265 | var sb = new StringBuilder(data); | ||
| 266 | sb.Remove(start, end - start + 1); | ||
| 267 | sb.Insert(start, value); | ||
| 268 | |||
| 269 | data = sb.ToString(); | ||
| 270 | changed = true; | ||
| 271 | |||
| 272 | end = start + value.Length; | ||
| 273 | } | ||
| 274 | |||
| 275 | start = data.IndexOf(ComponentIdPlaceholderStart, end); | ||
| 276 | } | ||
| 277 | |||
| 278 | if (changed) | ||
| 279 | { | ||
| 280 | field.Overwrite(data); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | } | ||
| 284 | } | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | /// <summary> | ||
| 291 | /// Uppercases the first character of a string. | ||
| 292 | /// </summary> | ||
| 293 | /// <param name="s">String to uppercase first character of.</param> | ||
| 294 | /// <returns>String with first character uppercased.</returns> | ||
| 295 | private static string UppercaseFirstChar(string s) | ||
| 296 | { | ||
| 297 | if (0 == s.Length) | ||
| 298 | { | ||
| 299 | return s; | ||
| 300 | } | ||
| 301 | |||
| 302 | return String.Concat(s.Substring(0, 1).ToUpperInvariant(), s.Substring(1)); | ||
| 303 | } | ||
| 304 | |||
| 305 | /// <summary> | ||
| 306 | /// Lowercases the string if present. | ||
| 307 | /// </summary> | ||
| 308 | /// <param name="s">String to lowercase.</param> | ||
| 309 | /// <returns>Null if the string is null, otherwise returns the lowercase.</returns> | ||
| 310 | private static string LowercaseOrNull(string s) | ||
| 311 | { | ||
| 312 | return s?.ToLowerInvariant(); | ||
| 313 | } | ||
| 314 | |||
| 315 | /// <summary> | ||
| 316 | /// Adds a search property to the active section. | ||
| 317 | /// </summary> | ||
| 318 | /// <param name="sourceLineNumbers">Current source/line number of processing.</param> | ||
| 319 | /// <param name="propertyId">Property to add to search.</param> | ||
| 320 | /// <param name="signature">Signature for search.</param> | ||
| 321 | private void AddAppSearch(SourceLineNumber sourceLineNumbers, Identifier propertyId, string signature) | ||
| 322 | { | ||
| 323 | if (!this.Core.EncounteredError) | ||
| 324 | { | ||
| 325 | if (propertyId.Id != propertyId.Id.ToUpperInvariant()) | ||
| 326 | { | ||
| 327 | this.Core.Write(ErrorMessages.SearchPropertyNotUppercase(sourceLineNumbers, "Property", "Id", propertyId.Id)); | ||
| 328 | } | ||
| 329 | |||
| 330 | this.Core.AddSymbol(new AppSearchSymbol(sourceLineNumbers, new Identifier(propertyId.Access, propertyId.Id, signature)) | ||
| 331 | { | ||
| 332 | PropertyRef = propertyId.Id, | ||
| 333 | SignatureRef = signature | ||
| 334 | }); | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | /// <summary> | ||
| 339 | /// Adds a property to the active section. | ||
| 340 | /// </summary> | ||
| 341 | /// <param name="sourceLineNumbers">Current source/line number of processing.</param> | ||
| 342 | /// <param name="propertyId">Identifier of property to add.</param> | ||
| 343 | /// <param name="value">Value of property.</param> | ||
| 344 | /// <param name="admin">Flag if property is an admin property.</param> | ||
| 345 | /// <param name="secure">Flag if property is a secure property.</param> | ||
| 346 | /// <param name="hidden">Flag if property is to be hidden.</param> | ||
| 347 | /// <param name="fragment">Adds the property to a new section.</param> | ||
| 348 | private void AddProperty(SourceLineNumber sourceLineNumbers, Identifier propertyId, string value, bool admin, bool secure, bool hidden, bool fragment) | ||
| 349 | { | ||
| 350 | // properties without a valid identifier should not be processed any further | ||
| 351 | if (null == propertyId || String.IsNullOrEmpty(propertyId.Id)) | ||
| 352 | { | ||
| 353 | return; | ||
| 354 | } | ||
| 355 | |||
| 356 | if (!String.IsNullOrEmpty(value)) | ||
| 357 | { | ||
| 358 | var start = value.IndexOf('['); | ||
| 359 | while (start != -1 && start < value.Length) | ||
| 360 | { | ||
| 361 | var end = value.IndexOf(']', start + 1); | ||
| 362 | if (end == -1) | ||
| 363 | { | ||
| 364 | break; | ||
| 365 | } | ||
| 366 | |||
| 367 | var id = value.Substring(start + 1, end - 1); | ||
| 368 | if (Common.IsIdentifier(id)) | ||
| 369 | { | ||
| 370 | this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, id)); | ||
| 371 | } | ||
| 372 | |||
| 373 | start = (end < value.Length) ? value.IndexOf('[', end + 1) : -1; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | if (!this.Core.EncounteredError) | ||
| 378 | { | ||
| 379 | var section = this.Core.ActiveSection; | ||
| 380 | |||
| 381 | // Add the symbol to a separate section if requested. | ||
| 382 | if (fragment) | ||
| 383 | { | ||
| 384 | var id = String.Concat(this.Core.ActiveSection.Id, ".", propertyId.Id); | ||
| 385 | |||
| 386 | section = this.Core.CreateSection(id, SectionType.Fragment, this.Context.CompilationId); | ||
| 387 | |||
| 388 | // Reference the property in the active section. | ||
| 389 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, propertyId.Id); | ||
| 390 | } | ||
| 391 | |||
| 392 | // Allow symbol to exist with no value so that PropertyRefs can be made for *Search elements | ||
| 393 | // the linker will remove these symbols before the final output is created. | ||
| 394 | section.AddSymbol(new PropertySymbol(sourceLineNumbers, propertyId) | ||
| 395 | { | ||
| 396 | Value = value, | ||
| 397 | }); | ||
| 398 | |||
| 399 | if (admin || hidden || secure) | ||
| 400 | { | ||
| 401 | this.AddWixPropertySymbol(sourceLineNumbers, propertyId, admin, secure, hidden, section); | ||
| 402 | } | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | private void AddWixPropertySymbol(SourceLineNumber sourceLineNumbers, Identifier property, bool admin, bool secure, bool hidden, IntermediateSection section = null) | ||
| 407 | { | ||
| 408 | if (secure && property.Id != property.Id.ToUpperInvariant()) | ||
| 409 | { | ||
| 410 | this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, "Property", "Id", property.Id)); | ||
| 411 | } | ||
| 412 | |||
| 413 | if (null == section) | ||
| 414 | { | ||
| 415 | section = this.Core.ActiveSection; | ||
| 416 | |||
| 417 | this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Property); // Property table is always required when using WixProperty table. | ||
| 418 | } | ||
| 419 | |||
| 420 | section.AddSymbol(new WixPropertySymbol(sourceLineNumbers) | ||
| 421 | { | ||
| 422 | PropertyRef = property.Id, | ||
| 423 | Admin = admin, | ||
| 424 | Hidden = hidden, | ||
| 425 | Secure = secure | ||
| 426 | }); | ||
| 427 | } | ||
| 428 | |||
| 429 | /// <summary> | ||
| 430 | /// Adds a "implemented category" registry key to active section. | ||
| 431 | /// </summary> | ||
| 432 | /// <param name="sourceLineNumbers">Current source/line number of processing.</param> | ||
| 433 | /// <param name="categoryId">GUID for category.</param> | ||
| 434 | /// <param name="classId">ClassId for to mark "implemented".</param> | ||
| 435 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 436 | private void RegisterImplementedCategories(SourceLineNumber sourceLineNumbers, string categoryId, string classId, string componentId) | ||
| 437 | { | ||
| 438 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Implemented Categories\\", categoryId), "*", null, componentId); | ||
| 439 | } | ||
| 440 | |||
| 441 | /// <summary> | ||
| 442 | /// Parses an application identifer element. | ||
| 443 | /// </summary> | ||
| 444 | /// <param name="node">Element to parse.</param> | ||
| 445 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 446 | /// <param name="advertise">The required advertise state (set depending upon the parent).</param> | ||
| 447 | /// <param name="fileServer">Optional file identifier for CLSID when not advertised.</param> | ||
| 448 | /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param> | ||
| 449 | /// <param name="typeLibVersion">Optional TypeLib Version for CLSID Interfaces (if any).</param> | ||
| 450 | private void ParseAppIdElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion) | ||
| 451 | { | ||
| 452 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 453 | string appId = null; | ||
| 454 | string remoteServerName = null; | ||
| 455 | string localService = null; | ||
| 456 | string serviceParameters = null; | ||
| 457 | string dllSurrogate = null; | ||
| 458 | bool? activateAtStorage = null; | ||
| 459 | var appIdAdvertise = YesNoType.NotSet; | ||
| 460 | bool? runAsInteractiveUser = null; | ||
| 461 | string description = null; | ||
| 462 | |||
| 463 | foreach (var attrib in node.Attributes()) | ||
| 464 | { | ||
| 465 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 466 | { | ||
| 467 | switch (attrib.Name.LocalName) | ||
| 468 | { | ||
| 469 | case "Id": | ||
| 470 | appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 471 | break; | ||
| 472 | case "ActivateAtStorage": | ||
| 473 | activateAtStorage = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 474 | break; | ||
| 475 | case "Advertise": | ||
| 476 | appIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 477 | break; | ||
| 478 | case "Description": | ||
| 479 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 480 | break; | ||
| 481 | case "DllSurrogate": | ||
| 482 | dllSurrogate = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 483 | break; | ||
| 484 | case "LocalService": | ||
| 485 | localService = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 486 | break; | ||
| 487 | case "RemoteServerName": | ||
| 488 | remoteServerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 489 | break; | ||
| 490 | case "RunAsInteractiveUser": | ||
| 491 | runAsInteractiveUser = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 492 | break; | ||
| 493 | case "ServiceParameters": | ||
| 494 | serviceParameters = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 495 | break; | ||
| 496 | default: | ||
| 497 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 498 | break; | ||
| 499 | } | ||
| 500 | } | ||
| 501 | else | ||
| 502 | { | ||
| 503 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 504 | } | ||
| 505 | } | ||
| 506 | |||
| 507 | if (null == appId) | ||
| 508 | { | ||
| 509 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 510 | } | ||
| 511 | |||
| 512 | if ((YesNoType.No == advertise && YesNoType.Yes == appIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == appIdAdvertise)) | ||
| 513 | { | ||
| 514 | this.Core.Write(ErrorMessages.AppIdIncompatibleAdvertiseState(sourceLineNumbers, node.Name.LocalName, "Advertise", appIdAdvertise.ToString(), advertise.ToString())); | ||
| 515 | } | ||
| 516 | else | ||
| 517 | { | ||
| 518 | advertise = appIdAdvertise; | ||
| 519 | } | ||
| 520 | |||
| 521 | // if the advertise state has not been set, default to non-advertised | ||
| 522 | if (YesNoType.NotSet == advertise) | ||
| 523 | { | ||
| 524 | advertise = YesNoType.No; | ||
| 525 | } | ||
| 526 | |||
| 527 | foreach (var child in node.Elements()) | ||
| 528 | { | ||
| 529 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 530 | { | ||
| 531 | switch (child.Name.LocalName) | ||
| 532 | { | ||
| 533 | case "Class": | ||
| 534 | this.ParseClassElement(child, componentId, advertise, fileServer, typeLibId, typeLibVersion, appId); | ||
| 535 | break; | ||
| 536 | default: | ||
| 537 | this.Core.UnexpectedElement(node, child); | ||
| 538 | break; | ||
| 539 | } | ||
| 540 | } | ||
| 541 | else | ||
| 542 | { | ||
| 543 | this.Core.ParseExtensionElement(node, child); | ||
| 544 | } | ||
| 545 | } | ||
| 546 | |||
| 547 | if (YesNoType.Yes == advertise) | ||
| 548 | { | ||
| 549 | if (null != description) | ||
| 550 | { | ||
| 551 | this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Description")); | ||
| 552 | } | ||
| 553 | |||
| 554 | if (!this.Core.EncounteredError) | ||
| 555 | { | ||
| 556 | this.Core.AddSymbol(new AppIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, appId)) | ||
| 557 | { | ||
| 558 | AppId = appId, | ||
| 559 | RemoteServerName = remoteServerName, | ||
| 560 | LocalService = localService, | ||
| 561 | ServiceParameters = serviceParameters, | ||
| 562 | DllSurrogate = dllSurrogate, | ||
| 563 | ActivateAtStorage = activateAtStorage, | ||
| 564 | RunAsInteractiveUser = runAsInteractiveUser, | ||
| 565 | }); | ||
| 566 | } | ||
| 567 | } | ||
| 568 | else if (YesNoType.No == advertise) | ||
| 569 | { | ||
| 570 | if (null != description) | ||
| 571 | { | ||
| 572 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), null, description, componentId); | ||
| 573 | } | ||
| 574 | else | ||
| 575 | { | ||
| 576 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "+", null, componentId); | ||
| 577 | } | ||
| 578 | |||
| 579 | if (null != remoteServerName) | ||
| 580 | { | ||
| 581 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RemoteServerName", remoteServerName, componentId); | ||
| 582 | } | ||
| 583 | |||
| 584 | if (null != localService) | ||
| 585 | { | ||
| 586 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "LocalService", localService, componentId); | ||
| 587 | } | ||
| 588 | |||
| 589 | if (null != serviceParameters) | ||
| 590 | { | ||
| 591 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ServiceParameters", serviceParameters, componentId); | ||
| 592 | } | ||
| 593 | |||
| 594 | if (null != dllSurrogate) | ||
| 595 | { | ||
| 596 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "DllSurrogate", dllSurrogate, componentId); | ||
| 597 | } | ||
| 598 | |||
| 599 | if (true == activateAtStorage) | ||
| 600 | { | ||
| 601 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ActivateAtStorage", "Y", componentId); | ||
| 602 | } | ||
| 603 | |||
| 604 | if (true == runAsInteractiveUser) | ||
| 605 | { | ||
| 606 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RunAs", "Interactive User", componentId); | ||
| 607 | } | ||
| 608 | } | ||
| 609 | } | ||
| 610 | |||
| 611 | /// <summary> | ||
| 612 | /// Parses an AssemblyName element. | ||
| 613 | /// </summary> | ||
| 614 | /// <param name="node">File element to parse.</param> | ||
| 615 | /// <param name="componentId">Parent's component id.</param> | ||
| 616 | private void ParseAssemblyName(XElement node, string componentId) | ||
| 617 | { | ||
| 618 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 619 | string id = null; | ||
| 620 | string value = null; | ||
| 621 | |||
| 622 | foreach (var attrib in node.Attributes()) | ||
| 623 | { | ||
| 624 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 625 | { | ||
| 626 | switch (attrib.Name.LocalName) | ||
| 627 | { | ||
| 628 | case "Id": | ||
| 629 | id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 630 | break; | ||
| 631 | case "Value": | ||
| 632 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 633 | break; | ||
| 634 | default: | ||
| 635 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 636 | break; | ||
| 637 | } | ||
| 638 | } | ||
| 639 | else | ||
| 640 | { | ||
| 641 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | if (null == id) | ||
| 646 | { | ||
| 647 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 648 | } | ||
| 649 | |||
| 650 | this.Core.ParseForExtensionElements(node); | ||
| 651 | |||
| 652 | if (!this.Core.EncounteredError) | ||
| 653 | { | ||
| 654 | this.Core.AddSymbol(new MsiAssemblyNameSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, componentId, id)) | ||
| 655 | { | ||
| 656 | ComponentRef = componentId, | ||
| 657 | Name = id, | ||
| 658 | Value = value, | ||
| 659 | }); | ||
| 660 | } | ||
| 661 | } | ||
| 662 | |||
| 663 | /// <summary> | ||
| 664 | /// Parses a binary element. | ||
| 665 | /// </summary> | ||
| 666 | /// <param name="node">Element to parse.</param> | ||
| 667 | /// <returns>Identifier for the new row.</returns> | ||
| 668 | private Identifier ParseBinaryElement(XElement node) | ||
| 669 | { | ||
| 670 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 671 | Identifier id = null; | ||
| 672 | string sourceFile = null; | ||
| 673 | var suppressModularization = YesNoType.NotSet; | ||
| 674 | |||
| 675 | foreach (var attrib in node.Attributes()) | ||
| 676 | { | ||
| 677 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 678 | { | ||
| 679 | switch (attrib.Name.LocalName) | ||
| 680 | { | ||
| 681 | case "Id": | ||
| 682 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 683 | break; | ||
| 684 | case "SourceFile": | ||
| 685 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 686 | break; | ||
| 687 | case "SuppressModularization": | ||
| 688 | suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 689 | break; | ||
| 690 | default: | ||
| 691 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 692 | break; | ||
| 693 | } | ||
| 694 | } | ||
| 695 | else | ||
| 696 | { | ||
| 697 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 698 | } | ||
| 699 | } | ||
| 700 | |||
| 701 | if (null == id) | ||
| 702 | { | ||
| 703 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 704 | id = Identifier.Invalid; | ||
| 705 | } | ||
| 706 | else if (!String.IsNullOrEmpty(id.Id)) // only check legal values | ||
| 707 | { | ||
| 708 | if (55 < id.Id.Length) | ||
| 709 | { | ||
| 710 | this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 55)); | ||
| 711 | } | ||
| 712 | else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized | ||
| 713 | { | ||
| 714 | if (18 < id.Id.Length) | ||
| 715 | { | ||
| 716 | this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 18)); | ||
| 717 | } | ||
| 718 | } | ||
| 719 | } | ||
| 720 | |||
| 721 | if (null == sourceFile) | ||
| 722 | { | ||
| 723 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
| 724 | } | ||
| 725 | |||
| 726 | this.Core.ParseForExtensionElements(node); | ||
| 727 | |||
| 728 | if (!this.Core.EncounteredError) | ||
| 729 | { | ||
| 730 | this.Core.AddSymbol(new BinarySymbol(sourceLineNumbers, id) | ||
| 731 | { | ||
| 732 | Data = new IntermediateFieldPathValue { Path = sourceFile } | ||
| 733 | }); | ||
| 734 | |||
| 735 | if (YesNoType.Yes == suppressModularization) | ||
| 736 | { | ||
| 737 | this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) | ||
| 738 | { | ||
| 739 | SuppressIdentifier = id.Id | ||
| 740 | }); | ||
| 741 | } | ||
| 742 | } | ||
| 743 | |||
| 744 | return id; | ||
| 745 | } | ||
| 746 | |||
| 747 | /// <summary> | ||
| 748 | /// Parses an icon element. | ||
| 749 | /// </summary> | ||
| 750 | /// <param name="node">Element to parse.</param> | ||
| 751 | /// <returns>Identifier for the new row.</returns> | ||
| 752 | private string ParseIconElement(XElement node) | ||
| 753 | { | ||
| 754 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 755 | Identifier id = null; | ||
| 756 | string sourceFile = null; | ||
| 757 | |||
| 758 | foreach (var attrib in node.Attributes()) | ||
| 759 | { | ||
| 760 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 761 | { | ||
| 762 | switch (attrib.Name.LocalName) | ||
| 763 | { | ||
| 764 | case "Id": | ||
| 765 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 766 | break; | ||
| 767 | case "SourceFile": | ||
| 768 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 769 | break; | ||
| 770 | default: | ||
| 771 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 772 | break; | ||
| 773 | } | ||
| 774 | } | ||
| 775 | else | ||
| 776 | { | ||
| 777 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 778 | } | ||
| 779 | } | ||
| 780 | |||
| 781 | if (null == id) | ||
| 782 | { | ||
| 783 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 784 | id = Identifier.Invalid; | ||
| 785 | } | ||
| 786 | else if (!String.IsNullOrEmpty(id.Id)) // only check legal values | ||
| 787 | { | ||
| 788 | if (57 < id.Id.Length) | ||
| 789 | { | ||
| 790 | this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 57)); | ||
| 791 | } | ||
| 792 | else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized | ||
| 793 | { | ||
| 794 | if (20 < id.Id.Length) | ||
| 795 | { | ||
| 796 | this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 20)); | ||
| 797 | } | ||
| 798 | } | ||
| 799 | } | ||
| 800 | |||
| 801 | if (null == sourceFile) | ||
| 802 | { | ||
| 803 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
| 804 | } | ||
| 805 | |||
| 806 | this.Core.ParseForExtensionElements(node); | ||
| 807 | |||
| 808 | if (!this.Core.EncounteredError) | ||
| 809 | { | ||
| 810 | this.Core.AddSymbol(new IconSymbol(sourceLineNumbers, id) | ||
| 811 | { | ||
| 812 | Data = new IntermediateFieldPathValue { Path = sourceFile }, | ||
| 813 | }); | ||
| 814 | } | ||
| 815 | |||
| 816 | return id.Id; | ||
| 817 | } | ||
| 818 | |||
| 819 | /// <summary> | ||
| 820 | /// Parses an InstanceTransforms element. | ||
| 821 | /// </summary> | ||
| 822 | /// <param name="node">Element to parse.</param> | ||
| 823 | private void ParseInstanceTransformsElement(XElement node) | ||
| 824 | { | ||
| 825 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 826 | string property = null; | ||
| 827 | |||
| 828 | foreach (var attrib in node.Attributes()) | ||
| 829 | { | ||
| 830 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 831 | { | ||
| 832 | switch (attrib.Name.LocalName) | ||
| 833 | { | ||
| 834 | case "Property": | ||
| 835 | property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 836 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); | ||
| 837 | break; | ||
| 838 | default: | ||
| 839 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 840 | break; | ||
| 841 | } | ||
| 842 | } | ||
| 843 | else | ||
| 844 | { | ||
| 845 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 846 | } | ||
| 847 | } | ||
| 848 | |||
| 849 | if (null == property) | ||
| 850 | { | ||
| 851 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); | ||
| 852 | } | ||
| 853 | |||
| 854 | // find unexpected child elements | ||
| 855 | foreach (var child in node.Elements()) | ||
| 856 | { | ||
| 857 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 858 | { | ||
| 859 | switch (child.Name.LocalName) | ||
| 860 | { | ||
| 861 | case "Instance": | ||
| 862 | this.ParseInstanceElement(child, property); | ||
| 863 | break; | ||
| 864 | default: | ||
| 865 | this.Core.UnexpectedElement(node, child); | ||
| 866 | break; | ||
| 867 | } | ||
| 868 | } | ||
| 869 | else | ||
| 870 | { | ||
| 871 | this.Core.ParseExtensionElement(node, child); | ||
| 872 | } | ||
| 873 | } | ||
| 874 | } | ||
| 875 | |||
| 876 | /// <summary> | ||
| 877 | /// Parses an instance element. | ||
| 878 | /// </summary> | ||
| 879 | /// <param name="node">Element to parse.</param> | ||
| 880 | /// <param name="propertyId">Identifier of instance property.</param> | ||
| 881 | private void ParseInstanceElement(XElement node, string propertyId) | ||
| 882 | { | ||
| 883 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 884 | Identifier id = null; | ||
| 885 | string productCode = null; | ||
| 886 | string productName = null; | ||
| 887 | string upgradeCode = null; | ||
| 888 | |||
| 889 | foreach (var attrib in node.Attributes()) | ||
| 890 | { | ||
| 891 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 892 | { | ||
| 893 | switch (attrib.Name.LocalName) | ||
| 894 | { | ||
| 895 | case "Id": | ||
| 896 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 897 | break; | ||
| 898 | case "ProductCode": | ||
| 899 | productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); | ||
| 900 | break; | ||
| 901 | case "ProductName": | ||
| 902 | productName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 903 | break; | ||
| 904 | case "UpgradeCode": | ||
| 905 | upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 906 | break; | ||
| 907 | default: | ||
| 908 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 909 | break; | ||
| 910 | } | ||
| 911 | } | ||
| 912 | else | ||
| 913 | { | ||
| 914 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 915 | } | ||
| 916 | } | ||
| 917 | |||
| 918 | if (null == id) | ||
| 919 | { | ||
| 920 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 921 | } | ||
| 922 | |||
| 923 | if (null == productCode) | ||
| 924 | { | ||
| 925 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductCode")); | ||
| 926 | } | ||
| 927 | |||
| 928 | this.Core.ParseForExtensionElements(node); | ||
| 929 | |||
| 930 | if (!this.Core.EncounteredError) | ||
| 931 | { | ||
| 932 | this.Core.AddSymbol(new WixInstanceTransformsSymbol(sourceLineNumbers, id) | ||
| 933 | { | ||
| 934 | PropertyId = propertyId, | ||
| 935 | ProductCode = productCode, | ||
| 936 | ProductName = productName, | ||
| 937 | UpgradeCode = upgradeCode | ||
| 938 | }); | ||
| 939 | } | ||
| 940 | } | ||
| 941 | |||
| 942 | /// <summary> | ||
| 943 | /// Parses a category element. | ||
| 944 | /// </summary> | ||
| 945 | /// <param name="node">Element to parse.</param> | ||
| 946 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 947 | private void ParseCategoryElement(XElement node, string componentId) | ||
| 948 | { | ||
| 949 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 950 | string id = null; | ||
| 951 | string appData = null; | ||
| 952 | string feature = null; | ||
| 953 | string qualifier = null; | ||
| 954 | |||
| 955 | foreach (var attrib in node.Attributes()) | ||
| 956 | { | ||
| 957 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 958 | { | ||
| 959 | switch (attrib.Name.LocalName) | ||
| 960 | { | ||
| 961 | case "Id": | ||
| 962 | id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 963 | break; | ||
| 964 | case "AppData": | ||
| 965 | appData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 966 | break; | ||
| 967 | case "Feature": | ||
| 968 | feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 969 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); | ||
| 970 | break; | ||
| 971 | case "Qualifier": | ||
| 972 | qualifier = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 973 | break; | ||
| 974 | default: | ||
| 975 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 976 | break; | ||
| 977 | } | ||
| 978 | } | ||
| 979 | else | ||
| 980 | { | ||
| 981 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 982 | } | ||
| 983 | } | ||
| 984 | |||
| 985 | if (null == id) | ||
| 986 | { | ||
| 987 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 988 | } | ||
| 989 | |||
| 990 | if (null == qualifier) | ||
| 991 | { | ||
| 992 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Qualifier")); | ||
| 993 | } | ||
| 994 | |||
| 995 | this.Core.ParseForExtensionElements(node); | ||
| 996 | |||
| 997 | if (!this.Core.EncounteredError) | ||
| 998 | { | ||
| 999 | this.Core.AddSymbol(new PublishComponentSymbol(sourceLineNumbers) | ||
| 1000 | { | ||
| 1001 | ComponentId = id, | ||
| 1002 | Qualifier = qualifier, | ||
| 1003 | ComponentRef = componentId, | ||
| 1004 | AppData = appData, | ||
| 1005 | FeatureRef = feature ?? Guid.Empty.ToString("B"), | ||
| 1006 | }); | ||
| 1007 | } | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | /// <summary> | ||
| 1011 | /// Parses a class element. | ||
| 1012 | /// </summary> | ||
| 1013 | /// <param name="node">Element to parse.</param> | ||
| 1014 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 1015 | /// <param name="advertise">Optional Advertise State for the parent AppId element (if any).</param> | ||
| 1016 | /// <param name="fileServer">Optional file identifier for CLSID when not advertised.</param> | ||
| 1017 | /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param> | ||
| 1018 | /// <param name="typeLibVersion">Optional TypeLib Version for CLSID Interfaces (if any).</param> | ||
| 1019 | /// <param name="parentAppId">Optional parent AppId.</param> | ||
| 1020 | private void ParseClassElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion, string parentAppId) | ||
| 1021 | { | ||
| 1022 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1023 | |||
| 1024 | string appId = null; | ||
| 1025 | string argument = null; | ||
| 1026 | var class16bit = false; | ||
| 1027 | var class32bit = false; | ||
| 1028 | string classId = null; | ||
| 1029 | var classAdvertise = YesNoType.NotSet; | ||
| 1030 | var contexts = new string[0]; | ||
| 1031 | string formattedContextString = null; | ||
| 1032 | var control = false; | ||
| 1033 | string defaultInprocHandler = null; | ||
| 1034 | string defaultProgId = null; | ||
| 1035 | string description = null; | ||
| 1036 | string fileTypeMask = null; | ||
| 1037 | string foreignServer = null; | ||
| 1038 | string icon = null; | ||
| 1039 | var iconIndex = CompilerConstants.IntegerNotSet; | ||
| 1040 | string insertable = null; | ||
| 1041 | string localFileServer = null; | ||
| 1042 | var programmable = false; | ||
| 1043 | var relativePath = YesNoType.NotSet; | ||
| 1044 | var safeForInit = false; | ||
| 1045 | var safeForScripting = false; | ||
| 1046 | var shortServerPath = false; | ||
| 1047 | string threadingModel = null; | ||
| 1048 | string version = null; | ||
| 1049 | |||
| 1050 | foreach (var attrib in node.Attributes()) | ||
| 1051 | { | ||
| 1052 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1053 | { | ||
| 1054 | switch (attrib.Name.LocalName) | ||
| 1055 | { | ||
| 1056 | case "Id": | ||
| 1057 | classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 1058 | break; | ||
| 1059 | case "Advertise": | ||
| 1060 | classAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1061 | break; | ||
| 1062 | case "AppId": | ||
| 1063 | appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 1064 | break; | ||
| 1065 | case "Argument": | ||
| 1066 | argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1067 | break; | ||
| 1068 | case "Context": | ||
| 1069 | contexts = this.Core.GetAttributeValue(sourceLineNumbers, attrib).Split("\r\n\t ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); | ||
| 1070 | break; | ||
| 1071 | case "Control": | ||
| 1072 | control = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1073 | break; | ||
| 1074 | case "Description": | ||
| 1075 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1076 | break; | ||
| 1077 | case "Handler": | ||
| 1078 | defaultInprocHandler = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1079 | break; | ||
| 1080 | case "Icon": | ||
| 1081 | icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 1082 | break; | ||
| 1083 | case "IconIndex": | ||
| 1084 | iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); | ||
| 1085 | break; | ||
| 1086 | case "RelativePath": | ||
| 1087 | relativePath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1088 | break; | ||
| 1089 | |||
| 1090 | // The following attributes result in rows always added to the Registry table rather than the Class table | ||
| 1091 | case "Insertable": | ||
| 1092 | insertable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? "Insertable" : "NotInsertable"; | ||
| 1093 | break; | ||
| 1094 | case "Programmable": | ||
| 1095 | programmable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1096 | break; | ||
| 1097 | case "SafeForInitializing": | ||
| 1098 | safeForInit = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1099 | break; | ||
| 1100 | case "SafeForScripting": | ||
| 1101 | safeForScripting = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1102 | break; | ||
| 1103 | case "ForeignServer": | ||
| 1104 | foreignServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1105 | break; | ||
| 1106 | case "Server": | ||
| 1107 | localFileServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1108 | break; | ||
| 1109 | case "ShortPath": | ||
| 1110 | shortServerPath = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1111 | break; | ||
| 1112 | case "ThreadingModel": | ||
| 1113 | threadingModel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1114 | break; | ||
| 1115 | case "Version": | ||
| 1116 | version = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1117 | break; | ||
| 1118 | default: | ||
| 1119 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1120 | break; | ||
| 1121 | } | ||
| 1122 | } | ||
| 1123 | else | ||
| 1124 | { | ||
| 1125 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1126 | } | ||
| 1127 | } | ||
| 1128 | |||
| 1129 | if (null == classId) | ||
| 1130 | { | ||
| 1131 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 1132 | } | ||
| 1133 | |||
| 1134 | var uniqueContexts = new HashSet<string>(); | ||
| 1135 | foreach (var context in contexts) | ||
| 1136 | { | ||
| 1137 | if (uniqueContexts.Contains(context)) | ||
| 1138 | { | ||
| 1139 | this.Core.Write(ErrorMessages.DuplicateContextValue(sourceLineNumbers, context)); | ||
| 1140 | } | ||
| 1141 | else | ||
| 1142 | { | ||
| 1143 | uniqueContexts.Add(context); | ||
| 1144 | } | ||
| 1145 | |||
| 1146 | if (context.EndsWith("32", StringComparison.Ordinal)) | ||
| 1147 | { | ||
| 1148 | class32bit = true; | ||
| 1149 | } | ||
| 1150 | else | ||
| 1151 | { | ||
| 1152 | class16bit = true; | ||
| 1153 | } | ||
| 1154 | } | ||
| 1155 | |||
| 1156 | if ((YesNoType.No == advertise && YesNoType.Yes == classAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == classAdvertise)) | ||
| 1157 | { | ||
| 1158 | this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, classAdvertise.ToString(), advertise.ToString())); | ||
| 1159 | } | ||
| 1160 | else | ||
| 1161 | { | ||
| 1162 | advertise = classAdvertise; | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | // If the advertise state has not been set, default to non-advertised. | ||
| 1166 | if (YesNoType.NotSet == advertise) | ||
| 1167 | { | ||
| 1168 | advertise = YesNoType.No; | ||
| 1169 | } | ||
| 1170 | |||
| 1171 | if (YesNoType.Yes == advertise && 0 == contexts.Length) | ||
| 1172 | { | ||
| 1173 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Context", "Advertise", "yes")); | ||
| 1174 | } | ||
| 1175 | |||
| 1176 | if (!String.IsNullOrEmpty(parentAppId) && !String.IsNullOrEmpty(appId)) | ||
| 1177 | { | ||
| 1178 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AppId", node.Parent.Name.LocalName)); | ||
| 1179 | } | ||
| 1180 | |||
| 1181 | if (!String.IsNullOrEmpty(localFileServer)) | ||
| 1182 | { | ||
| 1183 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, localFileServer); | ||
| 1184 | } | ||
| 1185 | |||
| 1186 | // Local variables used strictly for child node processing. | ||
| 1187 | var fileTypeMaskIndex = 0; | ||
| 1188 | var firstProgIdForClass = YesNoType.Yes; | ||
| 1189 | |||
| 1190 | foreach (var child in node.Elements()) | ||
| 1191 | { | ||
| 1192 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1193 | { | ||
| 1194 | switch (child.Name.LocalName) | ||
| 1195 | { | ||
| 1196 | case "FileTypeMask": | ||
| 1197 | if (YesNoType.Yes == advertise) | ||
| 1198 | { | ||
| 1199 | fileTypeMask = String.Concat(fileTypeMask, null == fileTypeMask ? String.Empty : ";", this.ParseFileTypeMaskElement(child)); | ||
| 1200 | } | ||
| 1201 | else if (YesNoType.No == advertise) | ||
| 1202 | { | ||
| 1203 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 1204 | this.Core.CreateRegistryRow(childSourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("FileType\\", classId, "\\", fileTypeMaskIndex.ToString()), String.Empty, this.ParseFileTypeMaskElement(child), componentId); | ||
| 1205 | fileTypeMaskIndex++; | ||
| 1206 | } | ||
| 1207 | break; | ||
| 1208 | case "Interface": | ||
| 1209 | this.ParseInterfaceElement(child, componentId, class16bit ? classId : null, class32bit ? classId : null, typeLibId, typeLibVersion); | ||
| 1210 | break; | ||
| 1211 | case "ProgId": | ||
| 1212 | { | ||
| 1213 | var foundExtension = false; | ||
| 1214 | var progId = this.ParseProgIdElement(child, componentId, advertise, classId, description, null, ref foundExtension, firstProgIdForClass); | ||
| 1215 | if (null == defaultProgId) | ||
| 1216 | { | ||
| 1217 | defaultProgId = progId; | ||
| 1218 | } | ||
| 1219 | firstProgIdForClass = YesNoType.No; | ||
| 1220 | } | ||
| 1221 | break; | ||
| 1222 | default: | ||
| 1223 | this.Core.UnexpectedElement(node, child); | ||
| 1224 | break; | ||
| 1225 | } | ||
| 1226 | } | ||
| 1227 | else | ||
| 1228 | { | ||
| 1229 | this.Core.ParseExtensionElement(node, child); | ||
| 1230 | } | ||
| 1231 | } | ||
| 1232 | |||
| 1233 | // If this Class is being advertised. | ||
| 1234 | if (YesNoType.Yes == advertise) | ||
| 1235 | { | ||
| 1236 | if (null != fileServer || null != localFileServer) | ||
| 1237 | { | ||
| 1238 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Server", "Advertise", "yes")); | ||
| 1239 | } | ||
| 1240 | |||
| 1241 | if (null != foreignServer) | ||
| 1242 | { | ||
| 1243 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Advertise", "yes")); | ||
| 1244 | } | ||
| 1245 | |||
| 1246 | if (null == appId && null != parentAppId) | ||
| 1247 | { | ||
| 1248 | appId = parentAppId; | ||
| 1249 | } | ||
| 1250 | |||
| 1251 | // add a Class row for each context | ||
| 1252 | if (!this.Core.EncounteredError) | ||
| 1253 | { | ||
| 1254 | foreach (var context in contexts) | ||
| 1255 | { | ||
| 1256 | var symbol = this.Core.AddSymbol(new ClassSymbol(sourceLineNumbers) | ||
| 1257 | { | ||
| 1258 | CLSID = classId, | ||
| 1259 | Context = context, | ||
| 1260 | ComponentRef = componentId, | ||
| 1261 | DefaultProgIdRef = defaultProgId, | ||
| 1262 | Description = description, | ||
| 1263 | FileTypeMask = fileTypeMask, | ||
| 1264 | DefInprocHandler = defaultInprocHandler, | ||
| 1265 | Argument = argument, | ||
| 1266 | FeatureRef = Guid.Empty.ToString("B"), | ||
| 1267 | RelativePath = YesNoType.Yes == relativePath, | ||
| 1268 | }); | ||
| 1269 | |||
| 1270 | if (null != appId) | ||
| 1271 | { | ||
| 1272 | symbol.AppIdRef = appId; | ||
| 1273 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.AppId, appId); | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | if (null != icon) | ||
| 1277 | { | ||
| 1278 | symbol.IconRef = icon; | ||
| 1279 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); | ||
| 1280 | } | ||
| 1281 | |||
| 1282 | if (CompilerConstants.IntegerNotSet != iconIndex) | ||
| 1283 | { | ||
| 1284 | symbol.IconIndex = iconIndex; | ||
| 1285 | } | ||
| 1286 | } | ||
| 1287 | } | ||
| 1288 | } | ||
| 1289 | else if (YesNoType.No == advertise) | ||
| 1290 | { | ||
| 1291 | if (null == fileServer && null == localFileServer && null == foreignServer) | ||
| 1292 | { | ||
| 1293 | this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); | ||
| 1294 | } | ||
| 1295 | |||
| 1296 | if (null != fileServer && null != foreignServer) | ||
| 1297 | { | ||
| 1298 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "File")); | ||
| 1299 | } | ||
| 1300 | else if (null != localFileServer && null != foreignServer) | ||
| 1301 | { | ||
| 1302 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); | ||
| 1303 | } | ||
| 1304 | else if (null == fileServer) | ||
| 1305 | { | ||
| 1306 | fileServer = localFileServer; | ||
| 1307 | } | ||
| 1308 | |||
| 1309 | if (null != appId) // need to use nesting (not a reference) for the unadvertised Class elements | ||
| 1310 | { | ||
| 1311 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AppId", "Advertise", "no")); | ||
| 1312 | } | ||
| 1313 | |||
| 1314 | // add the core registry keys for each context in the class | ||
| 1315 | foreach (var context in contexts) | ||
| 1316 | { | ||
| 1317 | if (context.StartsWith("InprocServer", StringComparison.Ordinal)) // dll server | ||
| 1318 | { | ||
| 1319 | if (null != argument) | ||
| 1320 | { | ||
| 1321 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Arguments", "Context", context)); | ||
| 1322 | } | ||
| 1323 | |||
| 1324 | if (null != fileServer) | ||
| 1325 | { | ||
| 1326 | formattedContextString = String.Concat("[", shortServerPath ? "!" : "#", fileServer, "]"); | ||
| 1327 | } | ||
| 1328 | else if (null != foreignServer) | ||
| 1329 | { | ||
| 1330 | formattedContextString = foreignServer; | ||
| 1331 | } | ||
| 1332 | } | ||
| 1333 | else if (context.StartsWith("LocalServer", StringComparison.Ordinal)) // exe server (quote the long path) | ||
| 1334 | { | ||
| 1335 | if (null != fileServer) | ||
| 1336 | { | ||
| 1337 | if (shortServerPath) | ||
| 1338 | { | ||
| 1339 | formattedContextString = String.Concat("[!", fileServer, "]"); | ||
| 1340 | } | ||
| 1341 | else | ||
| 1342 | { | ||
| 1343 | formattedContextString = String.Concat("\"[#", fileServer, "]\""); | ||
| 1344 | } | ||
| 1345 | } | ||
| 1346 | else if (null != foreignServer) | ||
| 1347 | { | ||
| 1348 | formattedContextString = foreignServer; | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | if (null != argument) | ||
| 1352 | { | ||
| 1353 | formattedContextString = String.Concat(formattedContextString, " ", argument); | ||
| 1354 | } | ||
| 1355 | } | ||
| 1356 | else | ||
| 1357 | { | ||
| 1358 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Context", context, "InprocServer", "InprocServer32", "LocalServer", "LocalServer32")); | ||
| 1359 | } | ||
| 1360 | |||
| 1361 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), String.Empty, formattedContextString, componentId); // ClassId context | ||
| 1362 | |||
| 1363 | if (null != icon) // ClassId default icon | ||
| 1364 | { | ||
| 1365 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); | ||
| 1366 | |||
| 1367 | icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); | ||
| 1368 | |||
| 1369 | if (CompilerConstants.IntegerNotSet != iconIndex) | ||
| 1370 | { | ||
| 1371 | icon = String.Concat(icon, ",", iconIndex); | ||
| 1372 | } | ||
| 1373 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\DefaultIcon"), String.Empty, icon, componentId); | ||
| 1374 | } | ||
| 1375 | } | ||
| 1376 | |||
| 1377 | if (null != parentAppId) // ClassId AppId (must be specified via nesting, not with the AppId attribute) | ||
| 1378 | { | ||
| 1379 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), "AppID", parentAppId, componentId); | ||
| 1380 | } | ||
| 1381 | |||
| 1382 | if (null != description) // ClassId description | ||
| 1383 | { | ||
| 1384 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), String.Empty, description, componentId); | ||
| 1385 | } | ||
| 1386 | |||
| 1387 | if (null != defaultInprocHandler) | ||
| 1388 | { | ||
| 1389 | switch (defaultInprocHandler) // ClassId Default Inproc Handler | ||
| 1390 | { | ||
| 1391 | case "1": | ||
| 1392 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); | ||
| 1393 | break; | ||
| 1394 | case "2": | ||
| 1395 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); | ||
| 1396 | break; | ||
| 1397 | case "3": | ||
| 1398 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); | ||
| 1399 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); | ||
| 1400 | break; | ||
| 1401 | default: | ||
| 1402 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, defaultInprocHandler, componentId); | ||
| 1403 | break; | ||
| 1404 | } | ||
| 1405 | } | ||
| 1406 | |||
| 1407 | if (YesNoType.NotSet != relativePath) // ClassId's RelativePath | ||
| 1408 | { | ||
| 1409 | this.Core.Write(ErrorMessages.RelativePathForRegistryElement(sourceLineNumbers)); | ||
| 1410 | } | ||
| 1411 | } | ||
| 1412 | |||
| 1413 | if (null != threadingModel) | ||
| 1414 | { | ||
| 1415 | threadingModel = Compiler.UppercaseFirstChar(threadingModel); | ||
| 1416 | |||
| 1417 | // add a threading model for each context in the class | ||
| 1418 | foreach (var context in contexts) | ||
| 1419 | { | ||
| 1420 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), "ThreadingModel", threadingModel, componentId); | ||
| 1421 | } | ||
| 1422 | } | ||
| 1423 | |||
| 1424 | if (null != typeLibId) | ||
| 1425 | { | ||
| 1426 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\TypeLib"), null, typeLibId, componentId); | ||
| 1427 | } | ||
| 1428 | |||
| 1429 | if (null != version) | ||
| 1430 | { | ||
| 1431 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Version"), null, version, componentId); | ||
| 1432 | } | ||
| 1433 | |||
| 1434 | if (null != insertable) | ||
| 1435 | { | ||
| 1436 | // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. | ||
| 1437 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", insertable), "*", null, componentId); | ||
| 1438 | } | ||
| 1439 | |||
| 1440 | if (control) | ||
| 1441 | { | ||
| 1442 | // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. | ||
| 1443 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Control"), "*", null, componentId); | ||
| 1444 | } | ||
| 1445 | |||
| 1446 | if (programmable) | ||
| 1447 | { | ||
| 1448 | // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. | ||
| 1449 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Programmable"), "*", null, componentId); | ||
| 1450 | } | ||
| 1451 | |||
| 1452 | if (safeForInit) | ||
| 1453 | { | ||
| 1454 | this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95802-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); | ||
| 1455 | } | ||
| 1456 | |||
| 1457 | if (safeForScripting) | ||
| 1458 | { | ||
| 1459 | this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95801-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); | ||
| 1460 | } | ||
| 1461 | } | ||
| 1462 | |||
| 1463 | /// <summary> | ||
| 1464 | /// Parses an Interface element. | ||
| 1465 | /// </summary> | ||
| 1466 | /// <param name="node">Element to parse.</param> | ||
| 1467 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 1468 | /// <param name="proxyId">16-bit proxy for interface.</param> | ||
| 1469 | /// <param name="proxyId32">32-bit proxy for interface.</param> | ||
| 1470 | /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param> | ||
| 1471 | /// <param name="typelibVersion">Version of the TypeLib to which this interface belongs. Required if typeLibId is specified</param> | ||
| 1472 | private void ParseInterfaceElement(XElement node, string componentId, string proxyId, string proxyId32, string typeLibId, string typelibVersion) | ||
| 1473 | { | ||
| 1474 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1475 | string baseInterface = null; | ||
| 1476 | string interfaceId = null; | ||
| 1477 | string name = null; | ||
| 1478 | var numMethods = CompilerConstants.IntegerNotSet; | ||
| 1479 | var versioned = true; | ||
| 1480 | |||
| 1481 | foreach (var attrib in node.Attributes()) | ||
| 1482 | { | ||
| 1483 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1484 | { | ||
| 1485 | switch (attrib.Name.LocalName) | ||
| 1486 | { | ||
| 1487 | case "Id": | ||
| 1488 | interfaceId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 1489 | break; | ||
| 1490 | case "BaseInterface": | ||
| 1491 | baseInterface = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 1492 | break; | ||
| 1493 | case "Name": | ||
| 1494 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1495 | break; | ||
| 1496 | case "NumMethods": | ||
| 1497 | numMethods = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 1498 | break; | ||
| 1499 | case "ProxyStubClassId": | ||
| 1500 | proxyId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib); | ||
| 1501 | break; | ||
| 1502 | case "ProxyStubClassId32": | ||
| 1503 | proxyId32 = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 1504 | break; | ||
| 1505 | case "Versioned": | ||
| 1506 | versioned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1507 | break; | ||
| 1508 | default: | ||
| 1509 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1510 | break; | ||
| 1511 | } | ||
| 1512 | } | ||
| 1513 | else | ||
| 1514 | { | ||
| 1515 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1516 | } | ||
| 1517 | } | ||
| 1518 | |||
| 1519 | if (null == interfaceId) | ||
| 1520 | { | ||
| 1521 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 1522 | } | ||
| 1523 | |||
| 1524 | if (null == name) | ||
| 1525 | { | ||
| 1526 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 1527 | } | ||
| 1528 | |||
| 1529 | this.Core.ParseForExtensionElements(node); | ||
| 1530 | |||
| 1531 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId), null, name, componentId); | ||
| 1532 | if (null != typeLibId) | ||
| 1533 | { | ||
| 1534 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), null, typeLibId, componentId); | ||
| 1535 | if (versioned) | ||
| 1536 | { | ||
| 1537 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), "Version", typelibVersion, componentId); | ||
| 1538 | } | ||
| 1539 | } | ||
| 1540 | |||
| 1541 | if (null != baseInterface) | ||
| 1542 | { | ||
| 1543 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\BaseInterface"), null, baseInterface, componentId); | ||
| 1544 | } | ||
| 1545 | |||
| 1546 | if (CompilerConstants.IntegerNotSet != numMethods) | ||
| 1547 | { | ||
| 1548 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\NumMethods"), null, numMethods.ToString(), componentId); | ||
| 1549 | } | ||
| 1550 | |||
| 1551 | if (null != proxyId) | ||
| 1552 | { | ||
| 1553 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid"), null, proxyId, componentId); | ||
| 1554 | } | ||
| 1555 | |||
| 1556 | if (null != proxyId32) | ||
| 1557 | { | ||
| 1558 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid32"), null, proxyId32, componentId); | ||
| 1559 | } | ||
| 1560 | } | ||
| 1561 | |||
| 1562 | /// <summary> | ||
| 1563 | /// Parses a CLSID's file type mask element. | ||
| 1564 | /// </summary> | ||
| 1565 | /// <param name="node">Element to parse.</param> | ||
| 1566 | /// <returns>String representing the file type mask elements.</returns> | ||
| 1567 | private string ParseFileTypeMaskElement(XElement node) | ||
| 1568 | { | ||
| 1569 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1570 | var cb = 0; | ||
| 1571 | var offset = CompilerConstants.IntegerNotSet; | ||
| 1572 | string mask = null; | ||
| 1573 | string value = null; | ||
| 1574 | |||
| 1575 | foreach (var attrib in node.Attributes()) | ||
| 1576 | { | ||
| 1577 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1578 | { | ||
| 1579 | switch (attrib.Name.LocalName) | ||
| 1580 | { | ||
| 1581 | case "Mask": | ||
| 1582 | mask = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1583 | break; | ||
| 1584 | case "Offset": | ||
| 1585 | offset = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 1586 | break; | ||
| 1587 | case "Value": | ||
| 1588 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1589 | break; | ||
| 1590 | default: | ||
| 1591 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1592 | break; | ||
| 1593 | } | ||
| 1594 | } | ||
| 1595 | else | ||
| 1596 | { | ||
| 1597 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1598 | } | ||
| 1599 | } | ||
| 1600 | |||
| 1601 | |||
| 1602 | if (null == mask) | ||
| 1603 | { | ||
| 1604 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Mask")); | ||
| 1605 | } | ||
| 1606 | |||
| 1607 | if (CompilerConstants.IntegerNotSet == offset) | ||
| 1608 | { | ||
| 1609 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); | ||
| 1610 | } | ||
| 1611 | |||
| 1612 | if (null == value) | ||
| 1613 | { | ||
| 1614 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 1615 | } | ||
| 1616 | |||
| 1617 | this.Core.ParseForExtensionElements(node); | ||
| 1618 | |||
| 1619 | if (!this.Core.EncounteredError) | ||
| 1620 | { | ||
| 1621 | if (mask.Length != value.Length) | ||
| 1622 | { | ||
| 1623 | this.Core.Write(ErrorMessages.ValueAndMaskMustBeSameLength(sourceLineNumbers)); | ||
| 1624 | } | ||
| 1625 | cb = mask.Length / 2; | ||
| 1626 | } | ||
| 1627 | |||
| 1628 | return String.Concat(offset.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", cb.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", mask, ",", value); | ||
| 1629 | } | ||
| 1630 | |||
| 1631 | /// <summary> | ||
| 1632 | /// Parses a product search element. | ||
| 1633 | /// </summary> | ||
| 1634 | /// <param name="node">Element to parse.</param> | ||
| 1635 | /// <param name="propertyId"></param> | ||
| 1636 | /// <returns>Signature for search element.</returns> | ||
| 1637 | private void ParseProductSearchElement(XElement node, string propertyId) | ||
| 1638 | { | ||
| 1639 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1640 | |||
| 1641 | string upgradeCode = null; | ||
| 1642 | string language = null; | ||
| 1643 | string maximum = null; | ||
| 1644 | string minimum = null; | ||
| 1645 | var excludeLanguages = false; | ||
| 1646 | var maxInclusive = false; | ||
| 1647 | var minInclusive = true; | ||
| 1648 | |||
| 1649 | foreach (var attrib in node.Attributes()) | ||
| 1650 | { | ||
| 1651 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1652 | { | ||
| 1653 | switch (attrib.Name.LocalName) | ||
| 1654 | { | ||
| 1655 | case "ExcludeLanguages": | ||
| 1656 | excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1657 | break; | ||
| 1658 | case "IncludeMaximum": | ||
| 1659 | maxInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1660 | break; | ||
| 1661 | case "IncludeMinimum": | ||
| 1662 | minInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1663 | break; | ||
| 1664 | case "Language": | ||
| 1665 | language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1666 | break; | ||
| 1667 | case "Minimum": | ||
| 1668 | minimum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1669 | break; | ||
| 1670 | case "Maximum": | ||
| 1671 | maximum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1672 | break; | ||
| 1673 | case "UpgradeCode": | ||
| 1674 | upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 1675 | break; | ||
| 1676 | default: | ||
| 1677 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1678 | break; | ||
| 1679 | } | ||
| 1680 | } | ||
| 1681 | else | ||
| 1682 | { | ||
| 1683 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1684 | } | ||
| 1685 | } | ||
| 1686 | |||
| 1687 | if (null == minimum && null == maximum) | ||
| 1688 | { | ||
| 1689 | this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); | ||
| 1690 | } | ||
| 1691 | |||
| 1692 | this.Core.ParseForExtensionElements(node); | ||
| 1693 | |||
| 1694 | if (!this.Core.EncounteredError) | ||
| 1695 | { | ||
| 1696 | this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) | ||
| 1697 | { | ||
| 1698 | UpgradeCode = upgradeCode, | ||
| 1699 | VersionMin = minimum, | ||
| 1700 | VersionMax = maximum, | ||
| 1701 | Language = language, | ||
| 1702 | ActionProperty = propertyId, | ||
| 1703 | OnlyDetect = true, | ||
| 1704 | ExcludeLanguages = excludeLanguages, | ||
| 1705 | VersionMaxInclusive = maxInclusive, | ||
| 1706 | VersionMinInclusive = minInclusive, | ||
| 1707 | }); | ||
| 1708 | } | ||
| 1709 | } | ||
| 1710 | |||
| 1711 | /// <summary> | ||
| 1712 | /// Parses a registry search element. | ||
| 1713 | /// </summary> | ||
| 1714 | /// <param name="node">Element to parse.</param> | ||
| 1715 | /// <returns>Signature for search element.</returns> | ||
| 1716 | private string ParseRegistrySearchElement(XElement node) | ||
| 1717 | { | ||
| 1718 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1719 | Identifier id = null; | ||
| 1720 | string key = null; | ||
| 1721 | string name = null; | ||
| 1722 | RegistryRootType? root = null; | ||
| 1723 | RegLocatorType? type = null; | ||
| 1724 | var search64bit = this.Context.IsCurrentPlatform64Bit; | ||
| 1725 | |||
| 1726 | foreach (var attrib in node.Attributes()) | ||
| 1727 | { | ||
| 1728 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1729 | { | ||
| 1730 | switch (attrib.Name.LocalName) | ||
| 1731 | { | ||
| 1732 | case "Id": | ||
| 1733 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1734 | break; | ||
| 1735 | case "Bitness": | ||
| 1736 | var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1737 | switch (bitnessValue) | ||
| 1738 | { | ||
| 1739 | case "always32": | ||
| 1740 | search64bit = false; | ||
| 1741 | break; | ||
| 1742 | case "always64": | ||
| 1743 | search64bit = true; | ||
| 1744 | break; | ||
| 1745 | case "default": | ||
| 1746 | case "": | ||
| 1747 | break; | ||
| 1748 | default: | ||
| 1749 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); | ||
| 1750 | break; | ||
| 1751 | } | ||
| 1752 | break; | ||
| 1753 | case "Key": | ||
| 1754 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1755 | break; | ||
| 1756 | case "Name": | ||
| 1757 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1758 | break; | ||
| 1759 | case "Root": | ||
| 1760 | root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, false); | ||
| 1761 | break; | ||
| 1762 | case "Type": | ||
| 1763 | var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1764 | switch (typeValue) | ||
| 1765 | { | ||
| 1766 | case "directory": | ||
| 1767 | type = RegLocatorType.Directory; | ||
| 1768 | break; | ||
| 1769 | case "file": | ||
| 1770 | type = RegLocatorType.FileName; | ||
| 1771 | break; | ||
| 1772 | case "raw": | ||
| 1773 | type = RegLocatorType.Raw; | ||
| 1774 | break; | ||
| 1775 | case "": | ||
| 1776 | break; | ||
| 1777 | default: | ||
| 1778 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "raw")); | ||
| 1779 | break; | ||
| 1780 | } | ||
| 1781 | break; | ||
| 1782 | default: | ||
| 1783 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1784 | break; | ||
| 1785 | } | ||
| 1786 | } | ||
| 1787 | else | ||
| 1788 | { | ||
| 1789 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1790 | } | ||
| 1791 | } | ||
| 1792 | |||
| 1793 | if (null == id) | ||
| 1794 | { | ||
| 1795 | id = this.Core.CreateIdentifier("reg", root.ToString(), key, name, type.ToString(), search64bit.ToString()); | ||
| 1796 | } | ||
| 1797 | |||
| 1798 | if (null == key) | ||
| 1799 | { | ||
| 1800 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 1801 | } | ||
| 1802 | |||
| 1803 | if (!root.HasValue) | ||
| 1804 | { | ||
| 1805 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); | ||
| 1806 | } | ||
| 1807 | |||
| 1808 | if (!type.HasValue) | ||
| 1809 | { | ||
| 1810 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); | ||
| 1811 | } | ||
| 1812 | |||
| 1813 | var signature = id.Id; | ||
| 1814 | var oneChild = false; | ||
| 1815 | foreach (var child in node.Elements()) | ||
| 1816 | { | ||
| 1817 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1818 | { | ||
| 1819 | switch (child.Name.LocalName) | ||
| 1820 | { | ||
| 1821 | case "DirectorySearch": | ||
| 1822 | if (oneChild) | ||
| 1823 | { | ||
| 1824 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 1825 | } | ||
| 1826 | oneChild = true; | ||
| 1827 | |||
| 1828 | // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column | ||
| 1829 | signature = this.ParseDirectorySearchElement(child, id.Id); | ||
| 1830 | break; | ||
| 1831 | case "DirectorySearchRef": | ||
| 1832 | if (oneChild) | ||
| 1833 | { | ||
| 1834 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 1835 | } | ||
| 1836 | oneChild = true; | ||
| 1837 | signature = this.ParseDirectorySearchRefElement(child, id.Id); | ||
| 1838 | break; | ||
| 1839 | case "FileSearch": | ||
| 1840 | if (oneChild) | ||
| 1841 | { | ||
| 1842 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 1843 | } | ||
| 1844 | oneChild = true; | ||
| 1845 | signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); | ||
| 1846 | id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures | ||
| 1847 | break; | ||
| 1848 | case "FileSearchRef": | ||
| 1849 | if (oneChild) | ||
| 1850 | { | ||
| 1851 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 1852 | } | ||
| 1853 | oneChild = true; | ||
| 1854 | var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures | ||
| 1855 | id = new Identifier(AccessModifier.Section, newId); | ||
| 1856 | signature = null; | ||
| 1857 | break; | ||
| 1858 | default: | ||
| 1859 | this.Core.UnexpectedElement(node, child); | ||
| 1860 | break; | ||
| 1861 | } | ||
| 1862 | } | ||
| 1863 | else | ||
| 1864 | { | ||
| 1865 | this.Core.ParseExtensionElement(node, child); | ||
| 1866 | } | ||
| 1867 | } | ||
| 1868 | |||
| 1869 | if (!this.Core.EncounteredError) | ||
| 1870 | { | ||
| 1871 | this.Core.AddSymbol(new RegLocatorSymbol(sourceLineNumbers, id) | ||
| 1872 | { | ||
| 1873 | Root = root.Value, | ||
| 1874 | Key = key, | ||
| 1875 | Name = name, | ||
| 1876 | Type = type.Value, | ||
| 1877 | Win64 = search64bit, | ||
| 1878 | }); | ||
| 1879 | } | ||
| 1880 | |||
| 1881 | return signature; | ||
| 1882 | } | ||
| 1883 | |||
| 1884 | /// <summary> | ||
| 1885 | /// Parses a registry search reference element. | ||
| 1886 | /// </summary> | ||
| 1887 | /// <param name="node">Element to parse.</param> | ||
| 1888 | /// <returns>Signature of referenced search element.</returns> | ||
| 1889 | private string ParseRegistrySearchRefElement(XElement node) | ||
| 1890 | { | ||
| 1891 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1892 | string id = null; | ||
| 1893 | |||
| 1894 | foreach (var attrib in node.Attributes()) | ||
| 1895 | { | ||
| 1896 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1897 | { | ||
| 1898 | switch (attrib.Name.LocalName) | ||
| 1899 | { | ||
| 1900 | case "Id": | ||
| 1901 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 1902 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.RegLocator, id); | ||
| 1903 | break; | ||
| 1904 | default: | ||
| 1905 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1906 | break; | ||
| 1907 | } | ||
| 1908 | } | ||
| 1909 | else | ||
| 1910 | { | ||
| 1911 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1912 | } | ||
| 1913 | } | ||
| 1914 | |||
| 1915 | if (null == id) | ||
| 1916 | { | ||
| 1917 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 1918 | } | ||
| 1919 | |||
| 1920 | this.Core.ParseForExtensionElements(node); | ||
| 1921 | |||
| 1922 | return id; // the id of the RegistrySearchRef element is its signature | ||
| 1923 | } | ||
| 1924 | |||
| 1925 | /// <summary> | ||
| 1926 | /// Parses child elements for search signatures. | ||
| 1927 | /// </summary> | ||
| 1928 | /// <param name="node">Node whose children we are parsing.</param> | ||
| 1929 | /// <returns>Returns list of string signatures.</returns> | ||
| 1930 | private List<string> ParseSearchSignatures(XElement node) | ||
| 1931 | { | ||
| 1932 | var signatures = new List<string>(); | ||
| 1933 | |||
| 1934 | foreach (var child in node.Elements()) | ||
| 1935 | { | ||
| 1936 | string signature = null; | ||
| 1937 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1938 | { | ||
| 1939 | switch (child.Name.LocalName) | ||
| 1940 | { | ||
| 1941 | case "ComplianceDrive": | ||
| 1942 | signature = this.ParseComplianceDriveElement(child); | ||
| 1943 | break; | ||
| 1944 | case "ComponentSearch": | ||
| 1945 | signature = this.ParseComponentSearchElement(child); | ||
| 1946 | break; | ||
| 1947 | case "DirectorySearch": | ||
| 1948 | signature = this.ParseDirectorySearchElement(child, String.Empty); | ||
| 1949 | break; | ||
| 1950 | case "DirectorySearchRef": | ||
| 1951 | signature = this.ParseDirectorySearchRefElement(child, String.Empty); | ||
| 1952 | break; | ||
| 1953 | case "IniFileSearch": | ||
| 1954 | signature = this.ParseIniFileSearchElement(child); | ||
| 1955 | break; | ||
| 1956 | case "ProductSearch": | ||
| 1957 | // handled in ParsePropertyElement | ||
| 1958 | break; | ||
| 1959 | case "RegistrySearch": | ||
| 1960 | signature = this.ParseRegistrySearchElement(child); | ||
| 1961 | break; | ||
| 1962 | case "RegistrySearchRef": | ||
| 1963 | signature = this.ParseRegistrySearchRefElement(child); | ||
| 1964 | break; | ||
| 1965 | default: | ||
| 1966 | this.Core.UnexpectedElement(node, child); | ||
| 1967 | break; | ||
| 1968 | } | ||
| 1969 | } | ||
| 1970 | else | ||
| 1971 | { | ||
| 1972 | this.Core.ParseExtensionElement(node, child); | ||
| 1973 | } | ||
| 1974 | |||
| 1975 | |||
| 1976 | if (!String.IsNullOrEmpty(signature)) | ||
| 1977 | { | ||
| 1978 | signatures.Add(signature); | ||
| 1979 | } | ||
| 1980 | } | ||
| 1981 | |||
| 1982 | return signatures; | ||
| 1983 | } | ||
| 1984 | |||
| 1985 | /// <summary> | ||
| 1986 | /// Parses a compliance drive element. | ||
| 1987 | /// </summary> | ||
| 1988 | /// <param name="node">Element to parse.</param> | ||
| 1989 | /// <returns>Signature of nested search elements.</returns> | ||
| 1990 | private string ParseComplianceDriveElement(XElement node) | ||
| 1991 | { | ||
| 1992 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1993 | string signature = null; | ||
| 1994 | |||
| 1995 | var oneChild = false; | ||
| 1996 | foreach (var child in node.Elements()) | ||
| 1997 | { | ||
| 1998 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1999 | { | ||
| 2000 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2001 | switch (child.Name.LocalName) | ||
| 2002 | { | ||
| 2003 | case "DirectorySearch": | ||
| 2004 | if (oneChild) | ||
| 2005 | { | ||
| 2006 | this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
| 2007 | } | ||
| 2008 | oneChild = true; | ||
| 2009 | signature = this.ParseDirectorySearchElement(child, "CCP_DRIVE"); | ||
| 2010 | break; | ||
| 2011 | case "DirectorySearchRef": | ||
| 2012 | if (oneChild) | ||
| 2013 | { | ||
| 2014 | this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
| 2015 | } | ||
| 2016 | oneChild = true; | ||
| 2017 | signature = this.ParseDirectorySearchRefElement(child, "CCP_DRIVE"); | ||
| 2018 | break; | ||
| 2019 | default: | ||
| 2020 | this.Core.UnexpectedElement(node, child); | ||
| 2021 | break; | ||
| 2022 | } | ||
| 2023 | } | ||
| 2024 | else | ||
| 2025 | { | ||
| 2026 | this.Core.ParseExtensionElement(node, child); | ||
| 2027 | } | ||
| 2028 | } | ||
| 2029 | |||
| 2030 | if (null == signature) | ||
| 2031 | { | ||
| 2032 | this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); | ||
| 2033 | } | ||
| 2034 | |||
| 2035 | return signature; | ||
| 2036 | } | ||
| 2037 | |||
| 2038 | /// <summary> | ||
| 2039 | /// Parses a compilance check element. | ||
| 2040 | /// </summary> | ||
| 2041 | /// <param name="node">Element to parse.</param> | ||
| 2042 | private void ParseComplianceCheckElement(XElement node) | ||
| 2043 | { | ||
| 2044 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2045 | |||
| 2046 | foreach (var attrib in node.Attributes()) | ||
| 2047 | { | ||
| 2048 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2049 | { | ||
| 2050 | switch (attrib.Name.LocalName) | ||
| 2051 | { | ||
| 2052 | default: | ||
| 2053 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2054 | break; | ||
| 2055 | } | ||
| 2056 | } | ||
| 2057 | else | ||
| 2058 | { | ||
| 2059 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2060 | } | ||
| 2061 | } | ||
| 2062 | |||
| 2063 | string signature = null; | ||
| 2064 | |||
| 2065 | // see if this property is used for appSearch | ||
| 2066 | var signatures = this.ParseSearchSignatures(node); | ||
| 2067 | foreach (var sig in signatures) | ||
| 2068 | { | ||
| 2069 | // if we haven't picked a signature for this ComplianceCheck pick | ||
| 2070 | // this one | ||
| 2071 | if (null == signature) | ||
| 2072 | { | ||
| 2073 | signature = sig; | ||
| 2074 | } | ||
| 2075 | else if (signature != sig) | ||
| 2076 | { | ||
| 2077 | // all signatures under a ComplianceCheck must be the same | ||
| 2078 | this.Core.Write(ErrorMessages.MultipleIdentifiersFound(sourceLineNumbers, node.Name.LocalName, sig, signature)); | ||
| 2079 | } | ||
| 2080 | } | ||
| 2081 | |||
| 2082 | if (null == signature) | ||
| 2083 | { | ||
| 2084 | this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); | ||
| 2085 | } | ||
| 2086 | |||
| 2087 | if (!this.Core.EncounteredError) | ||
| 2088 | { | ||
| 2089 | this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, signature))); | ||
| 2090 | } | ||
| 2091 | } | ||
| 2092 | |||
| 2093 | /// <summary> | ||
| 2094 | /// Parses a component element. | ||
| 2095 | /// </summary> | ||
| 2096 | /// <param name="node">Element to parse.</param> | ||
| 2097 | /// <param name="parentType">Type of component's complex reference parent. Will be Uknown if there is no parent.</param> | ||
| 2098 | /// <param name="parentId">Optional identifier for component's primary parent.</param> | ||
| 2099 | /// <param name="parentLanguage">Optional string for component's parent's language.</param> | ||
| 2100 | /// <param name="diskId">Optional disk id inherited from parent directory.</param> | ||
| 2101 | /// <param name="directoryId">Optional identifier for component's directory.</param> | ||
| 2102 | /// <param name="srcPath">Optional source path for files up to this point.</param> | ||
| 2103 | private void ParseComponentElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage, int diskId, string directoryId, string srcPath) | ||
| 2104 | { | ||
| 2105 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2106 | |||
| 2107 | var comPlusBits = CompilerConstants.IntegerNotSet; | ||
| 2108 | string condition = null; | ||
| 2109 | string subdirectory = null; | ||
| 2110 | var encounteredODBCDataSource = false; | ||
| 2111 | var files = 0; | ||
| 2112 | var guid = "*"; | ||
| 2113 | Identifier id = null; | ||
| 2114 | string componentIdPlaceholder = null; | ||
| 2115 | var keyFound = false; | ||
| 2116 | string keyPath = null; | ||
| 2117 | |||
| 2118 | var keyPathType = ComponentKeyPathType.Directory; | ||
| 2119 | var location = ComponentLocation.LocalOnly; | ||
| 2120 | var disableRegistryReflection = false; | ||
| 2121 | |||
| 2122 | var neverOverwrite = false; | ||
| 2123 | var permanent = false; | ||
| 2124 | var shared = false; | ||
| 2125 | var sharedDllRefCount = false; | ||
| 2126 | var transitive = false; | ||
| 2127 | var uninstallWhenSuperseded = false; | ||
| 2128 | var win64 = this.Context.IsCurrentPlatform64Bit; | ||
| 2129 | |||
| 2130 | var multiInstance = false; | ||
| 2131 | var symbols = new List<string>(); | ||
| 2132 | string feature = null; | ||
| 2133 | |||
| 2134 | foreach (var attrib in node.Attributes()) | ||
| 2135 | { | ||
| 2136 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2137 | { | ||
| 2138 | switch (attrib.Name.LocalName) | ||
| 2139 | { | ||
| 2140 | case "Id": | ||
| 2141 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2142 | break; | ||
| 2143 | case "Bitness": | ||
| 2144 | var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2145 | switch (bitnessValue) | ||
| 2146 | { | ||
| 2147 | case "always32": | ||
| 2148 | win64 = false; | ||
| 2149 | break; | ||
| 2150 | case "always64": | ||
| 2151 | win64 = true; | ||
| 2152 | break; | ||
| 2153 | case "default": | ||
| 2154 | case "": | ||
| 2155 | break; | ||
| 2156 | default: | ||
| 2157 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); | ||
| 2158 | break; | ||
| 2159 | } | ||
| 2160 | break; | ||
| 2161 | case "ComPlusFlags": | ||
| 2162 | comPlusBits = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 2163 | break; | ||
| 2164 | case "DisableRegistryReflection": | ||
| 2165 | disableRegistryReflection = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2166 | break; | ||
| 2167 | case "Condition": | ||
| 2168 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2169 | break; | ||
| 2170 | case "Directory": | ||
| 2171 | directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2172 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); | ||
| 2173 | break; | ||
| 2174 | case "Subdirectory": | ||
| 2175 | subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 2176 | break; | ||
| 2177 | case "DiskId": | ||
| 2178 | diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 2179 | break; | ||
| 2180 | case "Feature": | ||
| 2181 | feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2182 | break; | ||
| 2183 | case "Guid": | ||
| 2184 | guid = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true, true); | ||
| 2185 | break; | ||
| 2186 | case "KeyPath": | ||
| 2187 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 2188 | { | ||
| 2189 | keyFound = true; | ||
| 2190 | keyPath = null; | ||
| 2191 | } | ||
| 2192 | break; | ||
| 2193 | case "Location": | ||
| 2194 | var locationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2195 | switch (locationValue) | ||
| 2196 | { | ||
| 2197 | case "either": | ||
| 2198 | location = ComponentLocation.Either; | ||
| 2199 | break; | ||
| 2200 | case "local": // this is the default | ||
| 2201 | location = ComponentLocation.LocalOnly; | ||
| 2202 | break; | ||
| 2203 | case "source": | ||
| 2204 | location = ComponentLocation.SourceOnly; | ||
| 2205 | break; | ||
| 2206 | case "": | ||
| 2207 | break; | ||
| 2208 | default: | ||
| 2209 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, locationValue, "either", "local", "source")); | ||
| 2210 | break; | ||
| 2211 | } | ||
| 2212 | break; | ||
| 2213 | case "MultiInstance": | ||
| 2214 | multiInstance = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2215 | break; | ||
| 2216 | case "NeverOverwrite": | ||
| 2217 | neverOverwrite = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2218 | break; | ||
| 2219 | case "Permanent": | ||
| 2220 | permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2221 | break; | ||
| 2222 | case "Shared": | ||
| 2223 | shared = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2224 | break; | ||
| 2225 | case "SharedDllRefCount": | ||
| 2226 | sharedDllRefCount = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2227 | break; | ||
| 2228 | case "Transitive": | ||
| 2229 | transitive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2230 | break; | ||
| 2231 | case "UninstallWhenSuperseded": | ||
| 2232 | uninstallWhenSuperseded = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2233 | break; | ||
| 2234 | default: | ||
| 2235 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2236 | break; | ||
| 2237 | } | ||
| 2238 | } | ||
| 2239 | else | ||
| 2240 | { | ||
| 2241 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2242 | } | ||
| 2243 | } | ||
| 2244 | |||
| 2245 | if (id == null) | ||
| 2246 | { | ||
| 2247 | // Placeholder id for defaulting Component/@Id to keypath id. | ||
| 2248 | componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd); | ||
| 2249 | id = new Identifier(AccessModifier.Section, componentIdPlaceholder); | ||
| 2250 | } | ||
| 2251 | |||
| 2252 | if (String.IsNullOrEmpty(directoryId)) | ||
| 2253 | { | ||
| 2254 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory")); | ||
| 2255 | } | ||
| 2256 | |||
| 2257 | if (!String.IsNullOrEmpty(subdirectory)) | ||
| 2258 | { | ||
| 2259 | directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); | ||
| 2260 | } | ||
| 2261 | |||
| 2262 | if (String.IsNullOrEmpty(guid) && shared) | ||
| 2263 | { | ||
| 2264 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Shared", "yes", "Guid", "")); | ||
| 2265 | } | ||
| 2266 | |||
| 2267 | if (String.IsNullOrEmpty(guid) && permanent) | ||
| 2268 | { | ||
| 2269 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Permanent", "yes", "Guid", "")); | ||
| 2270 | } | ||
| 2271 | |||
| 2272 | if (null != feature) | ||
| 2273 | { | ||
| 2274 | if (this.compilingModule) | ||
| 2275 | { | ||
| 2276 | this.Core.Write(ErrorMessages.IllegalAttributeInMergeModule(sourceLineNumbers, node.Name.LocalName, "Feature")); | ||
| 2277 | } | ||
| 2278 | else | ||
| 2279 | { | ||
| 2280 | if (ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType) | ||
| 2281 | { | ||
| 2282 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Feature", node.Parent.Name.LocalName)); | ||
| 2283 | } | ||
| 2284 | else | ||
| 2285 | { | ||
| 2286 | this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); | ||
| 2287 | } | ||
| 2288 | } | ||
| 2289 | } | ||
| 2290 | |||
| 2291 | foreach (var child in node.Elements()) | ||
| 2292 | { | ||
| 2293 | var keyPathSet = YesNoType.NotSet; | ||
| 2294 | string keyPossible = null; | ||
| 2295 | ComponentKeyPathType? keyBit = null; | ||
| 2296 | |||
| 2297 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 2298 | { | ||
| 2299 | switch (child.Name.LocalName) | ||
| 2300 | { | ||
| 2301 | case "AppId": | ||
| 2302 | this.ParseAppIdElement(child, id.Id, YesNoType.NotSet, null, null, null); | ||
| 2303 | break; | ||
| 2304 | case "Category": | ||
| 2305 | this.ParseCategoryElement(child, id.Id); | ||
| 2306 | break; | ||
| 2307 | case "Class": | ||
| 2308 | this.ParseClassElement(child, id.Id, YesNoType.NotSet, null, null, null, null); | ||
| 2309 | break; | ||
| 2310 | case "CopyFile": | ||
| 2311 | this.ParseCopyFileElement(child, id.Id, null); | ||
| 2312 | break; | ||
| 2313 | case "CreateFolder": | ||
| 2314 | var createdFolder = this.ParseCreateFolderElement(child, id.Id, directoryId, win64); | ||
| 2315 | break; | ||
| 2316 | case "Environment": | ||
| 2317 | this.ParseEnvironmentElement(child, id.Id); | ||
| 2318 | break; | ||
| 2319 | case "Extension": | ||
| 2320 | this.ParseExtensionElement(child, id.Id, YesNoType.NotSet, null); | ||
| 2321 | break; | ||
| 2322 | case "File": | ||
| 2323 | keyPathSet = this.ParseFileElement(child, id.Id, directoryId, diskId, srcPath, out keyPossible, win64, guid); | ||
| 2324 | keyBit = ComponentKeyPathType.File; | ||
| 2325 | files++; | ||
| 2326 | break; | ||
| 2327 | case "IniFile": | ||
| 2328 | this.ParseIniFileElement(child, id.Id); | ||
| 2329 | break; | ||
| 2330 | case "Interface": | ||
| 2331 | this.ParseInterfaceElement(child, id.Id, null, null, null, null); | ||
| 2332 | break; | ||
| 2333 | case "IsolateComponent": | ||
| 2334 | this.ParseIsolateComponentElement(child, id.Id); | ||
| 2335 | break; | ||
| 2336 | case "ODBCDataSource": | ||
| 2337 | keyPathSet = this.ParseODBCDataSource(child, id.Id, null, out keyPossible); | ||
| 2338 | keyBit = ComponentKeyPathType.OdbcDataSource; | ||
| 2339 | encounteredODBCDataSource = true; | ||
| 2340 | break; | ||
| 2341 | case "ODBCDriver": | ||
| 2342 | this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCDriver); | ||
| 2343 | break; | ||
| 2344 | case "ODBCTranslator": | ||
| 2345 | this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCTranslator); | ||
| 2346 | break; | ||
| 2347 | case "ProgId": | ||
| 2348 | var foundExtension = false; | ||
| 2349 | this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet); | ||
| 2350 | break; | ||
| 2351 | case "Provides": | ||
| 2352 | if (win64) | ||
| 2353 | { | ||
| 2354 | this.Messaging.Write(CompilerWarnings.Win64Component(sourceLineNumbers, id.Id)); | ||
| 2355 | } | ||
| 2356 | |||
| 2357 | keyPathSet = this.ParseProvidesElement(child, null, id.Id, out keyPossible); | ||
| 2358 | keyBit = ComponentKeyPathType.Registry; | ||
| 2359 | break; | ||
| 2360 | |||
| 2361 | case "RegistryKey": | ||
| 2362 | keyPathSet = this.ParseRegistryKeyElement(child, id.Id, null, null, win64, out keyPossible); | ||
| 2363 | keyBit = ComponentKeyPathType.Registry; | ||
| 2364 | break; | ||
| 2365 | case "RegistryValue": | ||
| 2366 | keyPathSet = this.ParseRegistryValueElement(child, id.Id, null, null, win64, out keyPossible); | ||
| 2367 | keyBit = ComponentKeyPathType.Registry; | ||
| 2368 | break; | ||
| 2369 | case "RemoveFile": | ||
| 2370 | this.ParseRemoveFileElement(child, id.Id, directoryId); | ||
| 2371 | break; | ||
| 2372 | case "RemoveFolder": | ||
| 2373 | this.ParseRemoveFolderElement(child, id.Id, directoryId); | ||
| 2374 | break; | ||
| 2375 | case "RemoveRegistryKey": | ||
| 2376 | this.ParseRemoveRegistryKeyElement(child, id.Id); | ||
| 2377 | break; | ||
| 2378 | case "RemoveRegistryValue": | ||
| 2379 | this.ParseRemoveRegistryValueElement(child, id.Id); | ||
| 2380 | break; | ||
| 2381 | case "ReserveCost": | ||
| 2382 | this.ParseReserveCostElement(child, id.Id, directoryId); | ||
| 2383 | break; | ||
| 2384 | case "ServiceConfig": | ||
| 2385 | this.ParseServiceConfigElement(child, id.Id, null); | ||
| 2386 | break; | ||
| 2387 | case "ServiceConfigFailureActions": | ||
| 2388 | this.ParseServiceConfigFailureActionsElement(child, id.Id, null); | ||
| 2389 | break; | ||
| 2390 | case "ServiceControl": | ||
| 2391 | this.ParseServiceControlElement(child, id.Id); | ||
| 2392 | break; | ||
| 2393 | case "ServiceInstall": | ||
| 2394 | this.ParseServiceInstallElement(child, id.Id, win64); | ||
| 2395 | break; | ||
| 2396 | case "Shortcut": | ||
| 2397 | this.ParseShortcutElement(child, id.Id, node.Name.LocalName, directoryId, YesNoType.No); | ||
| 2398 | break; | ||
| 2399 | case "SymbolPath": | ||
| 2400 | symbols.Add(this.ParseSymbolPathElement(child)); | ||
| 2401 | break; | ||
| 2402 | case "TypeLib": | ||
| 2403 | this.ParseTypeLibElement(child, id.Id, null, win64); | ||
| 2404 | break; | ||
| 2405 | default: | ||
| 2406 | this.Core.UnexpectedElement(node, child); | ||
| 2407 | break; | ||
| 2408 | } | ||
| 2409 | } | ||
| 2410 | else | ||
| 2411 | { | ||
| 2412 | var context = new Dictionary<string, string>() { { "ComponentId", id?.Id }, { "DirectoryId", directoryId }, { "Win64", win64.ToString() }, }; | ||
| 2413 | var possibleKeyPath = this.Core.ParsePossibleKeyPathExtensionElement(node, child, context); | ||
| 2414 | if (null != possibleKeyPath) | ||
| 2415 | { | ||
| 2416 | if (PossibleKeyPathType.None == possibleKeyPath.Type) | ||
| 2417 | { | ||
| 2418 | keyPathSet = YesNoType.No; | ||
| 2419 | } | ||
| 2420 | else | ||
| 2421 | { | ||
| 2422 | keyPathSet = possibleKeyPath.Explicit ? YesNoType.Yes : YesNoType.NotSet; | ||
| 2423 | |||
| 2424 | if (!String.IsNullOrEmpty(possibleKeyPath.Id)) | ||
| 2425 | { | ||
| 2426 | keyPossible = possibleKeyPath.Id; | ||
| 2427 | } | ||
| 2428 | |||
| 2429 | if (PossibleKeyPathType.Registry == possibleKeyPath.Type || PossibleKeyPathType.RegistryFormatted == possibleKeyPath.Type) | ||
| 2430 | { | ||
| 2431 | keyBit = ComponentKeyPathType.Registry; //MsiInterop.MsidbComponentAttributesRegistryKeyPath; | ||
| 2432 | } | ||
| 2433 | } | ||
| 2434 | } | ||
| 2435 | } | ||
| 2436 | |||
| 2437 | // Verify that either the key path is not set, or it is set along with a key path ID. | ||
| 2438 | Debug.Assert(YesNoType.Yes != keyPathSet || (YesNoType.Yes == keyPathSet && null != keyPossible)); | ||
| 2439 | |||
| 2440 | if (keyFound && YesNoType.Yes == keyPathSet) | ||
| 2441 | { | ||
| 2442 | this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, node.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); | ||
| 2443 | } | ||
| 2444 | |||
| 2445 | // if a possible KeyPath has been found and that value was explicitly set as | ||
| 2446 | // the KeyPath of the component, set it now. Alternatively, if a possible | ||
| 2447 | // KeyPath has been found and no KeyPath has been previously set, use this | ||
| 2448 | // value as the default KeyPath of the component | ||
| 2449 | if (!String.IsNullOrEmpty(keyPossible) && (YesNoType.Yes == keyPathSet || (YesNoType.NotSet == keyPathSet && String.IsNullOrEmpty(keyPath) && !keyFound))) | ||
| 2450 | { | ||
| 2451 | keyFound = YesNoType.Yes == keyPathSet; | ||
| 2452 | keyPath = keyPossible; | ||
| 2453 | keyPathType = keyBit.Value; | ||
| 2454 | } | ||
| 2455 | } | ||
| 2456 | |||
| 2457 | // Check for conditions that exclude this component from using implicit ids and/or generated guids. | ||
| 2458 | var allowImplicitIds = true; | ||
| 2459 | if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType) | ||
| 2460 | { | ||
| 2461 | allowImplicitIds = false; | ||
| 2462 | if (guid == "*") | ||
| 2463 | { | ||
| 2464 | this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers)); | ||
| 2465 | } | ||
| 2466 | } | ||
| 2467 | else if (0 < files && ComponentKeyPathType.Registry == keyPathType) | ||
| 2468 | { | ||
| 2469 | allowImplicitIds = false; | ||
| 2470 | if (guid == "*") | ||
| 2471 | { | ||
| 2472 | this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true)); | ||
| 2473 | } | ||
| 2474 | } | ||
| 2475 | |||
| 2476 | // Check for implicit KeyPath which can easily be accidentally changed | ||
| 2477 | if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds) | ||
| 2478 | { | ||
| 2479 | this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id)); | ||
| 2480 | } | ||
| 2481 | |||
| 2482 | // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath. | ||
| 2483 | // either an explicit KeyPath="yes" attribute must be specified or requirements for | ||
| 2484 | // generatable guid must be met. | ||
| 2485 | if (componentIdPlaceholder == id.Id) | ||
| 2486 | { | ||
| 2487 | if (allowImplicitIds || keyFound && !String.IsNullOrEmpty(keyPath)) | ||
| 2488 | { | ||
| 2489 | this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath); | ||
| 2490 | |||
| 2491 | id = new Identifier(AccessModifier.Section, keyPath); | ||
| 2492 | } | ||
| 2493 | else | ||
| 2494 | { | ||
| 2495 | this.Core.Write(ErrorMessages.CannotDefaultComponentId(sourceLineNumbers)); | ||
| 2496 | } | ||
| 2497 | } | ||
| 2498 | |||
| 2499 | // finally add the Component table row | ||
| 2500 | if (!this.Core.EncounteredError) | ||
| 2501 | { | ||
| 2502 | this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) | ||
| 2503 | { | ||
| 2504 | ComponentId = guid, | ||
| 2505 | DirectoryRef = directoryId, | ||
| 2506 | Location = location, | ||
| 2507 | Condition = condition, | ||
| 2508 | KeyPath = keyPath, | ||
| 2509 | KeyPathType = keyPathType, | ||
| 2510 | DisableRegistryReflection = disableRegistryReflection, | ||
| 2511 | NeverOverwrite = neverOverwrite, | ||
| 2512 | Permanent = permanent, | ||
| 2513 | SharedDllRefCount = sharedDllRefCount, | ||
| 2514 | Shared = shared, | ||
| 2515 | Transitive = transitive, | ||
| 2516 | UninstallWhenSuperseded = uninstallWhenSuperseded, | ||
| 2517 | Win64 = win64, | ||
| 2518 | }); | ||
| 2519 | |||
| 2520 | if (multiInstance) | ||
| 2521 | { | ||
| 2522 | this.Core.AddSymbol(new WixInstanceComponentSymbol(sourceLineNumbers, id) | ||
| 2523 | { | ||
| 2524 | ComponentRef = id.Id, | ||
| 2525 | }); | ||
| 2526 | } | ||
| 2527 | |||
| 2528 | if (0 < symbols.Count) | ||
| 2529 | { | ||
| 2530 | this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Component, id.Id)) | ||
| 2531 | { | ||
| 2532 | SymbolType = SymbolPathType.Component, | ||
| 2533 | SymbolId = id.Id, | ||
| 2534 | SymbolPaths = String.Join(";", symbols), | ||
| 2535 | }); | ||
| 2536 | } | ||
| 2537 | |||
| 2538 | // Complus | ||
| 2539 | if (CompilerConstants.IntegerNotSet != comPlusBits) | ||
| 2540 | { | ||
| 2541 | this.Core.AddSymbol(new ComplusSymbol(sourceLineNumbers) | ||
| 2542 | { | ||
| 2543 | ComponentRef = id.Id, | ||
| 2544 | ExpType = comPlusBits, | ||
| 2545 | }); | ||
| 2546 | } | ||
| 2547 | |||
| 2548 | // if this is a module, automatically add this component to the references to ensure it gets in the ModuleComponents table | ||
| 2549 | if (this.compilingModule) | ||
| 2550 | { | ||
| 2551 | this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, ComplexReferenceChildType.Component, id.Id, false); | ||
| 2552 | } | ||
| 2553 | else if (ComplexReferenceParentType.Unknown != parentType && null != parentId) // if parent was provided, add a complex reference to that. | ||
| 2554 | { | ||
| 2555 | // If the Component is defined directly under a feature, then mark the complex reference primary. | ||
| 2556 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType); | ||
| 2557 | } | ||
| 2558 | } | ||
| 2559 | } | ||
| 2560 | |||
| 2561 | /// <summary> | ||
| 2562 | /// Parses a component group element. | ||
| 2563 | /// </summary> | ||
| 2564 | /// <param name="node">Element to parse.</param> | ||
| 2565 | /// <param name="parentType"></param> | ||
| 2566 | /// <param name="parentId"></param> | ||
| 2567 | private void ParseComponentGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 2568 | { | ||
| 2569 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2570 | Identifier id = null; | ||
| 2571 | string directoryId = null; | ||
| 2572 | string subdirectory = null; | ||
| 2573 | string source = null; | ||
| 2574 | |||
| 2575 | foreach (var attrib in node.Attributes()) | ||
| 2576 | { | ||
| 2577 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2578 | { | ||
| 2579 | switch (attrib.Name.LocalName) | ||
| 2580 | { | ||
| 2581 | case "Id": | ||
| 2582 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2583 | break; | ||
| 2584 | case "Directory": | ||
| 2585 | directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2586 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); | ||
| 2587 | break; | ||
| 2588 | case "Subdirectory": | ||
| 2589 | subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 2590 | break; | ||
| 2591 | case "Source": | ||
| 2592 | source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2593 | break; | ||
| 2594 | default: | ||
| 2595 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2596 | break; | ||
| 2597 | } | ||
| 2598 | } | ||
| 2599 | else | ||
| 2600 | { | ||
| 2601 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2602 | } | ||
| 2603 | } | ||
| 2604 | |||
| 2605 | if (null == id) | ||
| 2606 | { | ||
| 2607 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 2608 | id = Identifier.Invalid; | ||
| 2609 | } | ||
| 2610 | |||
| 2611 | directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); | ||
| 2612 | |||
| 2613 | if (!String.IsNullOrEmpty(source) && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) | ||
| 2614 | { | ||
| 2615 | source = String.Concat(source, Path.DirectorySeparatorChar); | ||
| 2616 | } | ||
| 2617 | |||
| 2618 | foreach (var child in node.Elements()) | ||
| 2619 | { | ||
| 2620 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 2621 | { | ||
| 2622 | switch (child.Name.LocalName) | ||
| 2623 | { | ||
| 2624 | case "ComponentGroupRef": | ||
| 2625 | this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); | ||
| 2626 | break; | ||
| 2627 | case "ComponentRef": | ||
| 2628 | this.ParseComponentRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); | ||
| 2629 | break; | ||
| 2630 | case "Component": | ||
| 2631 | this.ParseComponentElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null, CompilerConstants.IntegerNotSet, directoryId, source); | ||
| 2632 | break; | ||
| 2633 | default: | ||
| 2634 | this.Core.UnexpectedElement(node, child); | ||
| 2635 | break; | ||
| 2636 | } | ||
| 2637 | } | ||
| 2638 | else | ||
| 2639 | { | ||
| 2640 | this.Core.ParseExtensionElement(node, child); | ||
| 2641 | } | ||
| 2642 | } | ||
| 2643 | |||
| 2644 | if (!this.Core.EncounteredError) | ||
| 2645 | { | ||
| 2646 | this.Core.AddSymbol(new WixComponentGroupSymbol(sourceLineNumbers, id)); | ||
| 2647 | |||
| 2648 | // Add this componentGroup and its parent in WixGroup. | ||
| 2649 | this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ComponentGroup, id.Id); | ||
| 2650 | } | ||
| 2651 | } | ||
| 2652 | |||
| 2653 | /// <summary> | ||
| 2654 | /// Parses a component group reference element. | ||
| 2655 | /// </summary> | ||
| 2656 | /// <param name="node">Element to parse.</param> | ||
| 2657 | /// <param name="parentType">ComplexReferenceParentType of parent element.</param> | ||
| 2658 | /// <param name="parentId">Identifier of parent element (usually a Feature or Module).</param> | ||
| 2659 | /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param> | ||
| 2660 | private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) | ||
| 2661 | { | ||
| 2662 | Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); | ||
| 2663 | |||
| 2664 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2665 | string id = null; | ||
| 2666 | var primary = YesNoType.NotSet; | ||
| 2667 | |||
| 2668 | foreach (var attrib in node.Attributes()) | ||
| 2669 | { | ||
| 2670 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2671 | { | ||
| 2672 | switch (attrib.Name.LocalName) | ||
| 2673 | { | ||
| 2674 | case "Id": | ||
| 2675 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2676 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixComponentGroup, id); | ||
| 2677 | break; | ||
| 2678 | case "Primary": | ||
| 2679 | primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2680 | break; | ||
| 2681 | default: | ||
| 2682 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2683 | break; | ||
| 2684 | } | ||
| 2685 | } | ||
| 2686 | else | ||
| 2687 | { | ||
| 2688 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2689 | } | ||
| 2690 | } | ||
| 2691 | |||
| 2692 | if (null == id) | ||
| 2693 | { | ||
| 2694 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 2695 | } | ||
| 2696 | |||
| 2697 | this.Core.ParseForExtensionElements(node); | ||
| 2698 | |||
| 2699 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.ComponentGroup, id, (YesNoType.Yes == primary)); | ||
| 2700 | } | ||
| 2701 | |||
| 2702 | /// <summary> | ||
| 2703 | /// Parses a component reference element. | ||
| 2704 | /// </summary> | ||
| 2705 | /// <param name="node">Element to parse.</param> | ||
| 2706 | /// <param name="parentType">ComplexReferenceParentType of parent element.</param> | ||
| 2707 | /// <param name="parentId">Identifier of parent element (usually a Feature or Module).</param> | ||
| 2708 | /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param> | ||
| 2709 | private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) | ||
| 2710 | { | ||
| 2711 | Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); | ||
| 2712 | |||
| 2713 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2714 | string id = null; | ||
| 2715 | var primary = YesNoType.NotSet; | ||
| 2716 | |||
| 2717 | foreach (var attrib in node.Attributes()) | ||
| 2718 | { | ||
| 2719 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2720 | { | ||
| 2721 | switch (attrib.Name.LocalName) | ||
| 2722 | { | ||
| 2723 | case "Id": | ||
| 2724 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2725 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, id); | ||
| 2726 | break; | ||
| 2727 | case "Primary": | ||
| 2728 | primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2729 | break; | ||
| 2730 | default: | ||
| 2731 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2732 | break; | ||
| 2733 | } | ||
| 2734 | } | ||
| 2735 | else | ||
| 2736 | { | ||
| 2737 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2738 | } | ||
| 2739 | } | ||
| 2740 | |||
| 2741 | if (null == id) | ||
| 2742 | { | ||
| 2743 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 2744 | } | ||
| 2745 | |||
| 2746 | this.Core.ParseForExtensionElements(node); | ||
| 2747 | |||
| 2748 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id, (YesNoType.Yes == primary)); | ||
| 2749 | } | ||
| 2750 | |||
| 2751 | /// <summary> | ||
| 2752 | /// Parses a component search element. | ||
| 2753 | /// </summary> | ||
| 2754 | /// <param name="node">Element to parse.</param> | ||
| 2755 | /// <returns>Signature for search element.</returns> | ||
| 2756 | private string ParseComponentSearchElement(XElement node) | ||
| 2757 | { | ||
| 2758 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2759 | Identifier id = null; | ||
| 2760 | string componentId = null; | ||
| 2761 | var type = LocatorType.Filename; | ||
| 2762 | |||
| 2763 | foreach (var attrib in node.Attributes()) | ||
| 2764 | { | ||
| 2765 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2766 | { | ||
| 2767 | switch (attrib.Name.LocalName) | ||
| 2768 | { | ||
| 2769 | case "Id": | ||
| 2770 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2771 | break; | ||
| 2772 | case "Guid": | ||
| 2773 | componentId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 2774 | break; | ||
| 2775 | case "Type": | ||
| 2776 | var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2777 | switch (typeValue) | ||
| 2778 | { | ||
| 2779 | case "directory": | ||
| 2780 | type = LocatorType.Directory; | ||
| 2781 | break; | ||
| 2782 | case "file": | ||
| 2783 | type = LocatorType.Filename; | ||
| 2784 | break; | ||
| 2785 | case "": | ||
| 2786 | break; | ||
| 2787 | default: | ||
| 2788 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "directory", "file")); | ||
| 2789 | break; | ||
| 2790 | } | ||
| 2791 | break; | ||
| 2792 | default: | ||
| 2793 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2794 | break; | ||
| 2795 | } | ||
| 2796 | } | ||
| 2797 | else | ||
| 2798 | { | ||
| 2799 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2800 | } | ||
| 2801 | } | ||
| 2802 | |||
| 2803 | if (null == id) | ||
| 2804 | { | ||
| 2805 | id = this.Core.CreateIdentifier("cmp", componentId, type.ToString()); | ||
| 2806 | } | ||
| 2807 | |||
| 2808 | var signature = id.Id; | ||
| 2809 | var oneChild = false; | ||
| 2810 | foreach (var child in node.Elements()) | ||
| 2811 | { | ||
| 2812 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 2813 | { | ||
| 2814 | switch (child.Name.LocalName) | ||
| 2815 | { | ||
| 2816 | case "DirectorySearch": | ||
| 2817 | if (oneChild) | ||
| 2818 | { | ||
| 2819 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 2820 | } | ||
| 2821 | oneChild = true; | ||
| 2822 | |||
| 2823 | // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column | ||
| 2824 | signature = this.ParseDirectorySearchElement(child, id.Id); | ||
| 2825 | break; | ||
| 2826 | case "DirectorySearchRef": | ||
| 2827 | if (oneChild) | ||
| 2828 | { | ||
| 2829 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 2830 | } | ||
| 2831 | oneChild = true; | ||
| 2832 | signature = this.ParseDirectorySearchRefElement(child, id.Id); | ||
| 2833 | break; | ||
| 2834 | case "FileSearch": | ||
| 2835 | if (oneChild) | ||
| 2836 | { | ||
| 2837 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 2838 | } | ||
| 2839 | oneChild = true; | ||
| 2840 | signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); | ||
| 2841 | id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures | ||
| 2842 | break; | ||
| 2843 | case "FileSearchRef": | ||
| 2844 | if (oneChild) | ||
| 2845 | { | ||
| 2846 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 2847 | } | ||
| 2848 | oneChild = true; | ||
| 2849 | var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures | ||
| 2850 | id = new Identifier(AccessModifier.Section, newId); | ||
| 2851 | signature = null; | ||
| 2852 | break; | ||
| 2853 | default: | ||
| 2854 | this.Core.UnexpectedElement(node, child); | ||
| 2855 | break; | ||
| 2856 | } | ||
| 2857 | } | ||
| 2858 | else | ||
| 2859 | { | ||
| 2860 | this.Core.ParseExtensionElement(node, child); | ||
| 2861 | } | ||
| 2862 | } | ||
| 2863 | |||
| 2864 | if (!this.Core.EncounteredError) | ||
| 2865 | { | ||
| 2866 | this.Core.AddSymbol(new CompLocatorSymbol(sourceLineNumbers, id) | ||
| 2867 | { | ||
| 2868 | SignatureRef = id.Id, | ||
| 2869 | ComponentId = componentId, | ||
| 2870 | Type = type, | ||
| 2871 | }); | ||
| 2872 | } | ||
| 2873 | |||
| 2874 | return signature; | ||
| 2875 | } | ||
| 2876 | |||
| 2877 | /// <summary> | ||
| 2878 | /// Parses a create folder element. | ||
| 2879 | /// </summary> | ||
| 2880 | /// <param name="node">Element to parse.</param> | ||
| 2881 | /// <param name="componentId">Identifier for parent component.</param> | ||
| 2882 | /// <param name="directoryId">Default identifier for directory to create.</param> | ||
| 2883 | /// <param name="win64Component">true if the component is 64-bit.</param> | ||
| 2884 | /// <returns>Identifier for the directory that will be created</returns> | ||
| 2885 | private string ParseCreateFolderElement(XElement node, string componentId, string directoryId, bool win64Component) | ||
| 2886 | { | ||
| 2887 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2888 | string subdirectory = null; | ||
| 2889 | |||
| 2890 | foreach (var attrib in node.Attributes()) | ||
| 2891 | { | ||
| 2892 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2893 | { | ||
| 2894 | switch (attrib.Name.LocalName) | ||
| 2895 | { | ||
| 2896 | case "Directory": | ||
| 2897 | directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2898 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); | ||
| 2899 | break; | ||
| 2900 | case "Subdirectory": | ||
| 2901 | subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 2902 | break; | ||
| 2903 | default: | ||
| 2904 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2905 | break; | ||
| 2906 | } | ||
| 2907 | } | ||
| 2908 | else | ||
| 2909 | { | ||
| 2910 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2911 | } | ||
| 2912 | } | ||
| 2913 | |||
| 2914 | directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); | ||
| 2915 | |||
| 2916 | foreach (var child in node.Elements()) | ||
| 2917 | { | ||
| 2918 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 2919 | { | ||
| 2920 | switch (child.Name.LocalName) | ||
| 2921 | { | ||
| 2922 | case "Shortcut": | ||
| 2923 | this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No); | ||
| 2924 | break; | ||
| 2925 | case "Permission": | ||
| 2926 | this.ParsePermissionElement(child, directoryId, "CreateFolder"); | ||
| 2927 | break; | ||
| 2928 | case "PermissionEx": | ||
| 2929 | this.ParsePermissionExElement(child, directoryId, "CreateFolder"); | ||
| 2930 | break; | ||
| 2931 | default: | ||
| 2932 | this.Core.UnexpectedElement(node, child); | ||
| 2933 | break; | ||
| 2934 | } | ||
| 2935 | } | ||
| 2936 | else | ||
| 2937 | { | ||
| 2938 | var context = new Dictionary<string, string>() { { "DirectoryId", directoryId }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; | ||
| 2939 | this.Core.ParseExtensionElement(node, child, context); | ||
| 2940 | } | ||
| 2941 | } | ||
| 2942 | |||
| 2943 | if (!this.Core.EncounteredError) | ||
| 2944 | { | ||
| 2945 | this.Core.AddSymbol(new CreateFolderSymbol(sourceLineNumbers) | ||
| 2946 | { | ||
| 2947 | DirectoryRef = directoryId, | ||
| 2948 | ComponentRef = componentId, | ||
| 2949 | }); | ||
| 2950 | } | ||
| 2951 | |||
| 2952 | return directoryId; | ||
| 2953 | } | ||
| 2954 | |||
| 2955 | /// <summary> | ||
| 2956 | /// Parses a copy file element. | ||
| 2957 | /// </summary> | ||
| 2958 | /// <param name="node">Element to parse.</param> | ||
| 2959 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 2960 | /// <param name="fileId">Identifier of file to copy (null if moving the file).</param> | ||
| 2961 | private void ParseCopyFileElement(XElement node, string componentId, string fileId) | ||
| 2962 | { | ||
| 2963 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2964 | Identifier id = null; | ||
| 2965 | var delete = false; | ||
| 2966 | string destinationDirectory = null; | ||
| 2967 | string destinationSubdirectory = null; | ||
| 2968 | string destinationName = null; | ||
| 2969 | string destinationShortName = null; | ||
| 2970 | string destinationProperty = null; | ||
| 2971 | string sourceDirectory = null; | ||
| 2972 | string sourceSubdirectory = null; | ||
| 2973 | string sourceFolder = null; | ||
| 2974 | string sourceName = null; | ||
| 2975 | string sourceProperty = null; | ||
| 2976 | |||
| 2977 | foreach (var attrib in node.Attributes()) | ||
| 2978 | { | ||
| 2979 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2980 | { | ||
| 2981 | switch (attrib.Name.LocalName) | ||
| 2982 | { | ||
| 2983 | case "Id": | ||
| 2984 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2985 | break; | ||
| 2986 | case "Delete": | ||
| 2987 | delete = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2988 | break; | ||
| 2989 | case "DestinationDirectory": | ||
| 2990 | destinationDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2991 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, destinationDirectory); | ||
| 2992 | break; | ||
| 2993 | case "DestinationSubdirectory": | ||
| 2994 | destinationSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 2995 | break; | ||
| 2996 | case "DestinationName": | ||
| 2997 | destinationName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib); | ||
| 2998 | break; | ||
| 2999 | case "DestinationProperty": | ||
| 3000 | destinationProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3001 | break; | ||
| 3002 | case "DestinationShortName": | ||
| 3003 | destinationShortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib); | ||
| 3004 | break; | ||
| 3005 | case "FileId": | ||
| 3006 | if (null != fileId) | ||
| 3007 | { | ||
| 3008 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); | ||
| 3009 | } | ||
| 3010 | fileId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3011 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, fileId); | ||
| 3012 | break; | ||
| 3013 | case "SourceDirectory": | ||
| 3014 | sourceDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3015 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, sourceDirectory); | ||
| 3016 | break; | ||
| 3017 | case "SourceSubdirectory": | ||
| 3018 | sourceSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 3019 | break; | ||
| 3020 | case "SourceName": | ||
| 3021 | sourceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3022 | break; | ||
| 3023 | case "SourceProperty": | ||
| 3024 | sourceProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3025 | break; | ||
| 3026 | default: | ||
| 3027 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3028 | break; | ||
| 3029 | } | ||
| 3030 | } | ||
| 3031 | else | ||
| 3032 | { | ||
| 3033 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3034 | } | ||
| 3035 | } | ||
| 3036 | |||
| 3037 | if (null != sourceFolder && null != sourceDirectory) // SourceFolder and SourceDirectory cannot coexist | ||
| 3038 | { | ||
| 3039 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceDirectory")); | ||
| 3040 | } | ||
| 3041 | |||
| 3042 | if (null != sourceFolder && null != sourceProperty) // SourceFolder and SourceProperty cannot coexist | ||
| 3043 | { | ||
| 3044 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceProperty")); | ||
| 3045 | } | ||
| 3046 | |||
| 3047 | if (null != sourceDirectory && null != sourceProperty) // SourceDirectory and SourceProperty cannot coexist | ||
| 3048 | { | ||
| 3049 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "SourceDirectory")); | ||
| 3050 | } | ||
| 3051 | |||
| 3052 | sourceDirectory = this.HandleSubdirectory(sourceLineNumbers, node, sourceDirectory, sourceSubdirectory, "SourceDirectory", "SourceSubdirectory"); | ||
| 3053 | |||
| 3054 | if (null != destinationDirectory && null != destinationProperty) // DestinationDirectory and DestinationProperty cannot coexist | ||
| 3055 | { | ||
| 3056 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationProperty", "DestinationDirectory")); | ||
| 3057 | } | ||
| 3058 | |||
| 3059 | destinationDirectory = this.HandleSubdirectory(sourceLineNumbers, node, destinationDirectory, destinationSubdirectory, "DestinationDirectory", "DestinationSubdirectory"); | ||
| 3060 | |||
| 3061 | if (null == id) | ||
| 3062 | { | ||
| 3063 | id = this.Core.CreateIdentifier("cf", sourceFolder, sourceDirectory, sourceProperty, destinationDirectory, destinationProperty, destinationName); | ||
| 3064 | } | ||
| 3065 | |||
| 3066 | this.Core.ParseForExtensionElements(node); | ||
| 3067 | |||
| 3068 | if (null == fileId) | ||
| 3069 | { | ||
| 3070 | // DestinationDirectory or DestinationProperty must be specified | ||
| 3071 | if (null == destinationDirectory && null == destinationProperty) | ||
| 3072 | { | ||
| 3073 | this.Core.Write(ErrorMessages.ExpectedAttributesWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationDirectory", "DestinationProperty", "FileId")); | ||
| 3074 | } | ||
| 3075 | |||
| 3076 | if (!this.Core.EncounteredError) | ||
| 3077 | { | ||
| 3078 | this.Core.AddSymbol(new MoveFileSymbol(sourceLineNumbers, id) | ||
| 3079 | { | ||
| 3080 | ComponentRef = componentId, | ||
| 3081 | SourceName = sourceName, | ||
| 3082 | DestinationName = destinationName, | ||
| 3083 | DestinationShortName = destinationShortName, | ||
| 3084 | SourceFolder = sourceDirectory ?? sourceProperty, | ||
| 3085 | DestFolder = destinationDirectory ?? destinationProperty, | ||
| 3086 | Delete = delete, | ||
| 3087 | }); | ||
| 3088 | } | ||
| 3089 | } | ||
| 3090 | else // copy the file | ||
| 3091 | { | ||
| 3092 | if (null != sourceDirectory) | ||
| 3093 | { | ||
| 3094 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceDirectory", "FileId")); | ||
| 3095 | } | ||
| 3096 | |||
| 3097 | if (null != sourceFolder) | ||
| 3098 | { | ||
| 3099 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "FileId")); | ||
| 3100 | } | ||
| 3101 | |||
| 3102 | if (null != sourceName) | ||
| 3103 | { | ||
| 3104 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceName", "FileId")); | ||
| 3105 | } | ||
| 3106 | |||
| 3107 | if (null != sourceProperty) | ||
| 3108 | { | ||
| 3109 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "FileId")); | ||
| 3110 | } | ||
| 3111 | |||
| 3112 | if (delete) | ||
| 3113 | { | ||
| 3114 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Delete", "FileId")); | ||
| 3115 | } | ||
| 3116 | |||
| 3117 | if (null == destinationName && null == destinationDirectory && null == destinationProperty) | ||
| 3118 | { | ||
| 3119 | this.Core.Write(WarningMessages.CopyFileFileIdUseless(sourceLineNumbers)); | ||
| 3120 | } | ||
| 3121 | |||
| 3122 | if (!this.Core.EncounteredError) | ||
| 3123 | { | ||
| 3124 | this.Core.AddSymbol(new DuplicateFileSymbol(sourceLineNumbers, id) | ||
| 3125 | { | ||
| 3126 | ComponentRef = componentId, | ||
| 3127 | FileRef = fileId, | ||
| 3128 | DestinationName = destinationName, | ||
| 3129 | DestinationShortName = destinationShortName, | ||
| 3130 | DestinationFolder = destinationDirectory ?? destinationProperty, | ||
| 3131 | }); | ||
| 3132 | } | ||
| 3133 | } | ||
| 3134 | } | ||
| 3135 | |||
| 3136 | /// <summary> | ||
| 3137 | /// Parses a CustomAction element. | ||
| 3138 | /// </summary> | ||
| 3139 | /// <param name="node">Element to parse.</param> | ||
| 3140 | private void ParseCustomActionElement(XElement node) | ||
| 3141 | { | ||
| 3142 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3143 | Identifier id = null; | ||
| 3144 | var inlineScript = false; | ||
| 3145 | var suppressModularization = YesNoType.NotSet; | ||
| 3146 | string source = null; | ||
| 3147 | string target = null; | ||
| 3148 | var explicitWin64 = false; | ||
| 3149 | |||
| 3150 | string scriptFile = null; | ||
| 3151 | string subdirectory = null; | ||
| 3152 | |||
| 3153 | CustomActionSourceType? sourceType = null; | ||
| 3154 | CustomActionTargetType? targetType = null; | ||
| 3155 | var executionType = CustomActionExecutionType.Immediate; | ||
| 3156 | var hidden = false; | ||
| 3157 | var impersonate = true; | ||
| 3158 | var patchUninstall = false; | ||
| 3159 | var tsAware = false; | ||
| 3160 | var win64 = false; | ||
| 3161 | var async = false; | ||
| 3162 | var ignoreResult = false; | ||
| 3163 | |||
| 3164 | foreach (var attrib in node.Attributes()) | ||
| 3165 | { | ||
| 3166 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3167 | { | ||
| 3168 | switch (attrib.Name.LocalName) | ||
| 3169 | { | ||
| 3170 | case "Id": | ||
| 3171 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 3172 | break; | ||
| 3173 | case "BinaryRef": | ||
| 3174 | if (null != source) | ||
| 3175 | { | ||
| 3176 | this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); | ||
| 3177 | } | ||
| 3178 | source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3179 | sourceType = CustomActionSourceType.Binary; | ||
| 3180 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary | ||
| 3181 | break; | ||
| 3182 | case "Bitness": | ||
| 3183 | var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3184 | switch (bitnessValue) | ||
| 3185 | { | ||
| 3186 | case "always32": | ||
| 3187 | explicitWin64 = true; | ||
| 3188 | win64 = false; | ||
| 3189 | break; | ||
| 3190 | case "always64": | ||
| 3191 | explicitWin64 = true; | ||
| 3192 | win64 = true; | ||
| 3193 | break; | ||
| 3194 | case "default": | ||
| 3195 | case "": | ||
| 3196 | break; | ||
| 3197 | default: | ||
| 3198 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); | ||
| 3199 | break; | ||
| 3200 | } | ||
| 3201 | break; | ||
| 3202 | case "Directory": | ||
| 3203 | if (null != source) | ||
| 3204 | { | ||
| 3205 | this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileRef", "Property", "Script")); | ||
| 3206 | } | ||
| 3207 | source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3208 | sourceType = CustomActionSourceType.Directory; | ||
| 3209 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, source); | ||
| 3210 | break; | ||
| 3211 | case "DllEntry": | ||
| 3212 | if (null != target) | ||
| 3213 | { | ||
| 3214 | this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); | ||
| 3215 | } | ||
| 3216 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3217 | targetType = CustomActionTargetType.Dll; | ||
| 3218 | break; | ||
| 3219 | case "Error": | ||
| 3220 | if (null != target) | ||
| 3221 | { | ||
| 3222 | this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); | ||
| 3223 | } | ||
| 3224 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3225 | sourceType = CustomActionSourceType.File; | ||
| 3226 | targetType = CustomActionTargetType.TextData; | ||
| 3227 | |||
| 3228 | // The target can be either a formatted error string or a literal | ||
| 3229 | // error number. Try to convert to error number to determine whether | ||
| 3230 | // to add a reference. No need to look at the value. | ||
| 3231 | if (Int32.TryParse(target, out var ignored)) | ||
| 3232 | { | ||
| 3233 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Error, target); | ||
| 3234 | } | ||
| 3235 | break; | ||
| 3236 | case "ExeCommand": | ||
| 3237 | if (null != target) | ||
| 3238 | { | ||
| 3239 | this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); | ||
| 3240 | } | ||
| 3241 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid | ||
| 3242 | targetType = CustomActionTargetType.Exe; | ||
| 3243 | break; | ||
| 3244 | case "Execute": | ||
| 3245 | var execute = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3246 | switch (execute) | ||
| 3247 | { | ||
| 3248 | case "commit": | ||
| 3249 | executionType = CustomActionExecutionType.Commit; | ||
| 3250 | break; | ||
| 3251 | case "deferred": | ||
| 3252 | executionType = CustomActionExecutionType.Deferred; | ||
| 3253 | break; | ||
| 3254 | case "firstSequence": | ||
| 3255 | executionType = CustomActionExecutionType.FirstSequence; | ||
| 3256 | break; | ||
| 3257 | case "immediate": | ||
| 3258 | executionType = CustomActionExecutionType.Immediate; | ||
| 3259 | break; | ||
| 3260 | case "oncePerProcess": | ||
| 3261 | executionType = CustomActionExecutionType.OncePerProcess; | ||
| 3262 | break; | ||
| 3263 | case "rollback": | ||
| 3264 | executionType = CustomActionExecutionType.Rollback; | ||
| 3265 | break; | ||
| 3266 | case "secondSequence": | ||
| 3267 | executionType = CustomActionExecutionType.ClientRepeat; | ||
| 3268 | break; | ||
| 3269 | default: | ||
| 3270 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, execute, "commit", "deferred", "firstSequence", "immediate", "oncePerProcess", "rollback", "secondSequence")); | ||
| 3271 | break; | ||
| 3272 | } | ||
| 3273 | break; | ||
| 3274 | case "FileRef": | ||
| 3275 | if (null != source) | ||
| 3276 | { | ||
| 3277 | this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); | ||
| 3278 | } | ||
| 3279 | source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3280 | sourceType = CustomActionSourceType.File; | ||
| 3281 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File | ||
| 3282 | break; | ||
| 3283 | case "HideTarget": | ||
| 3284 | hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3285 | break; | ||
| 3286 | case "Impersonate": | ||
| 3287 | impersonate = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3288 | break; | ||
| 3289 | case "JScriptCall": | ||
| 3290 | if (null != target) | ||
| 3291 | { | ||
| 3292 | this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); | ||
| 3293 | } | ||
| 3294 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid | ||
| 3295 | targetType = CustomActionTargetType.JScript; | ||
| 3296 | break; | ||
| 3297 | case "PatchUninstall": | ||
| 3298 | patchUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3299 | break; | ||
| 3300 | case "Property": | ||
| 3301 | if (null != source) | ||
| 3302 | { | ||
| 3303 | this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); | ||
| 3304 | } | ||
| 3305 | source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3306 | sourceType = CustomActionSourceType.Property; | ||
| 3307 | break; | ||
| 3308 | case "Return": | ||
| 3309 | var returnValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3310 | switch (returnValue) | ||
| 3311 | { | ||
| 3312 | case "asyncNoWait": | ||
| 3313 | async = true; | ||
| 3314 | ignoreResult = true; | ||
| 3315 | break; | ||
| 3316 | case "asyncWait": | ||
| 3317 | async = true; | ||
| 3318 | break; | ||
| 3319 | case "check": | ||
| 3320 | break; | ||
| 3321 | case "ignore": | ||
| 3322 | ignoreResult = true; | ||
| 3323 | break; | ||
| 3324 | case "": | ||
| 3325 | break; | ||
| 3326 | default: | ||
| 3327 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, returnValue, "asyncNoWait", "asyncWait", "check", "ignore")); | ||
| 3328 | break; | ||
| 3329 | } | ||
| 3330 | break; | ||
| 3331 | case "Script": | ||
| 3332 | if (null != source) | ||
| 3333 | { | ||
| 3334 | this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); | ||
| 3335 | } | ||
| 3336 | |||
| 3337 | if (null != target) | ||
| 3338 | { | ||
| 3339 | this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); | ||
| 3340 | } | ||
| 3341 | |||
| 3342 | // set the source and target to empty string for error messages when the user sets multiple sources or targets | ||
| 3343 | source = String.Empty; | ||
| 3344 | target = String.Empty; | ||
| 3345 | |||
| 3346 | inlineScript = true; | ||
| 3347 | |||
| 3348 | var script = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3349 | switch (script) | ||
| 3350 | { | ||
| 3351 | case "jscript": | ||
| 3352 | sourceType = CustomActionSourceType.Directory; | ||
| 3353 | targetType = CustomActionTargetType.JScript; | ||
| 3354 | break; | ||
| 3355 | case "vbscript": | ||
| 3356 | sourceType = CustomActionSourceType.Directory; | ||
| 3357 | targetType = CustomActionTargetType.VBScript; | ||
| 3358 | break; | ||
| 3359 | case "": | ||
| 3360 | break; | ||
| 3361 | default: | ||
| 3362 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, script, "jscript", "vbscript")); | ||
| 3363 | break; | ||
| 3364 | } | ||
| 3365 | break; | ||
| 3366 | case "ScriptSourceFile": | ||
| 3367 | scriptFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3368 | break; | ||
| 3369 | case "Subdirectory": | ||
| 3370 | subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 3371 | break; | ||
| 3372 | case "SuppressModularization": | ||
| 3373 | suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3374 | break; | ||
| 3375 | case "TerminalServerAware": | ||
| 3376 | tsAware = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3377 | break; | ||
| 3378 | case "Value": | ||
| 3379 | if (null != target) | ||
| 3380 | { | ||
| 3381 | this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); | ||
| 3382 | } | ||
| 3383 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid | ||
| 3384 | targetType = CustomActionTargetType.TextData; | ||
| 3385 | break; | ||
| 3386 | case "VBScriptCall": | ||
| 3387 | if (null != target) | ||
| 3388 | { | ||
| 3389 | this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); | ||
| 3390 | } | ||
| 3391 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid | ||
| 3392 | targetType = CustomActionTargetType.VBScript; | ||
| 3393 | break; | ||
| 3394 | default: | ||
| 3395 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3396 | break; | ||
| 3397 | } | ||
| 3398 | } | ||
| 3399 | else | ||
| 3400 | { | ||
| 3401 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3402 | } | ||
| 3403 | } | ||
| 3404 | |||
| 3405 | if (null == id) | ||
| 3406 | { | ||
| 3407 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3408 | id = Identifier.Invalid; | ||
| 3409 | } | ||
| 3410 | |||
| 3411 | if (!explicitWin64 && this.Context.IsCurrentPlatform64Bit && (CustomActionTargetType.VBScript == targetType || CustomActionTargetType.JScript == targetType)) | ||
| 3412 | { | ||
| 3413 | win64 = true; | ||
| 3414 | } | ||
| 3415 | |||
| 3416 | if (!String.IsNullOrEmpty(subdirectory)) | ||
| 3417 | { | ||
| 3418 | if (sourceType == CustomActionSourceType.Directory) | ||
| 3419 | { | ||
| 3420 | source = this.HandleSubdirectory(sourceLineNumbers, node, source, subdirectory, "Directory", "Subdirectory"); | ||
| 3421 | } | ||
| 3422 | else | ||
| 3423 | { | ||
| 3424 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Subdirectory", "Directory")); | ||
| 3425 | } | ||
| 3426 | } | ||
| 3427 | |||
| 3428 | // if we have an in-lined Script CustomAction ensure no source or target attributes were provided | ||
| 3429 | if (inlineScript) | ||
| 3430 | { | ||
| 3431 | if (String.IsNullOrEmpty(scriptFile)) | ||
| 3432 | { | ||
| 3433 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); | ||
| 3434 | } | ||
| 3435 | } | ||
| 3436 | else if (CustomActionTargetType.VBScript == targetType) // non-inline vbscript | ||
| 3437 | { | ||
| 3438 | if (null == source) | ||
| 3439 | { | ||
| 3440 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryRef", "FileRef", "Property")); | ||
| 3441 | } | ||
| 3442 | else if (CustomActionSourceType.Directory == sourceType) | ||
| 3443 | { | ||
| 3444 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "Directory")); | ||
| 3445 | } | ||
| 3446 | } | ||
| 3447 | else if (CustomActionTargetType.JScript == targetType) // non-inline jscript | ||
| 3448 | { | ||
| 3449 | if (null == source) | ||
| 3450 | { | ||
| 3451 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryRef", "FileRef", "Property")); | ||
| 3452 | } | ||
| 3453 | else if (CustomActionSourceType.Directory == sourceType) | ||
| 3454 | { | ||
| 3455 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "Directory")); | ||
| 3456 | } | ||
| 3457 | } | ||
| 3458 | else if (CustomActionTargetType.Exe == targetType) // exe-command | ||
| 3459 | { | ||
| 3460 | if (null == source) | ||
| 3461 | { | ||
| 3462 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryRef", "Directory", "FileRef", "Property")); | ||
| 3463 | } | ||
| 3464 | } | ||
| 3465 | else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType && CustomActionSourceType.File != sourceType) | ||
| 3466 | { | ||
| 3467 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property", "Error")); | ||
| 3468 | } | ||
| 3469 | |||
| 3470 | if (!inlineScript && !String.IsNullOrEmpty(scriptFile)) | ||
| 3471 | { | ||
| 3472 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); | ||
| 3473 | } | ||
| 3474 | |||
| 3475 | if (win64 && CustomActionTargetType.VBScript != targetType && CustomActionTargetType.JScript != targetType) | ||
| 3476 | { | ||
| 3477 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Win64", "Script", "VBScriptCall", "JScriptCall")); | ||
| 3478 | } | ||
| 3479 | |||
| 3480 | if (async && ignoreResult && CustomActionTargetType.Exe != targetType) | ||
| 3481 | { | ||
| 3482 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Return", "asyncNoWait", "ExeCommand")); | ||
| 3483 | } | ||
| 3484 | |||
| 3485 | // TS-aware CAs are valid only when deferred. | ||
| 3486 | if (tsAware & | ||
| 3487 | CustomActionExecutionType.Deferred != executionType && | ||
| 3488 | CustomActionExecutionType.Rollback != executionType && | ||
| 3489 | CustomActionExecutionType.Commit != executionType) | ||
| 3490 | { | ||
| 3491 | this.Core.Write(ErrorMessages.IllegalTerminalServerCustomActionAttributes(sourceLineNumbers)); | ||
| 3492 | } | ||
| 3493 | |||
| 3494 | // MSI doesn't support in-script property setting, so disallow it | ||
| 3495 | if (CustomActionSourceType.Property == sourceType && | ||
| 3496 | CustomActionTargetType.TextData == targetType && | ||
| 3497 | (CustomActionExecutionType.Deferred == executionType || | ||
| 3498 | CustomActionExecutionType.Rollback == executionType || | ||
| 3499 | CustomActionExecutionType.Commit == executionType)) | ||
| 3500 | { | ||
| 3501 | this.Core.Write(ErrorMessages.IllegalPropertyCustomActionAttributes(sourceLineNumbers)); | ||
| 3502 | } | ||
| 3503 | |||
| 3504 | if (!targetType.HasValue /*0 == targetBits*/) | ||
| 3505 | { | ||
| 3506 | this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); | ||
| 3507 | } | ||
| 3508 | |||
| 3509 | this.Core.ParseForExtensionElements(node); | ||
| 3510 | |||
| 3511 | if (!this.Core.EncounteredError) | ||
| 3512 | { | ||
| 3513 | this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, id) | ||
| 3514 | { | ||
| 3515 | ExecutionType = executionType, | ||
| 3516 | Source = source, | ||
| 3517 | SourceType = sourceType.Value, | ||
| 3518 | Target = target, | ||
| 3519 | TargetType = targetType.Value, | ||
| 3520 | Async = async, | ||
| 3521 | IgnoreResult = ignoreResult, | ||
| 3522 | Impersonate = impersonate, | ||
| 3523 | PatchUninstall = patchUninstall, | ||
| 3524 | TSAware = tsAware, | ||
| 3525 | Win64 = win64, | ||
| 3526 | Hidden = hidden, | ||
| 3527 | ScriptFile = new IntermediateFieldPathValue { Path = scriptFile } | ||
| 3528 | }); | ||
| 3529 | |||
| 3530 | if (YesNoType.Yes == suppressModularization) | ||
| 3531 | { | ||
| 3532 | this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) | ||
| 3533 | { | ||
| 3534 | SuppressIdentifier = id.Id | ||
| 3535 | }); | ||
| 3536 | } | ||
| 3537 | } | ||
| 3538 | } | ||
| 3539 | |||
| 3540 | /// <summary> | ||
| 3541 | /// Parses a simple reference element. | ||
| 3542 | /// </summary> | ||
| 3543 | /// <param name="node">Element to parse.</param> | ||
| 3544 | /// <param name="symbolDefinition">Symbol which contains the target of the simple reference.</param> | ||
| 3545 | /// <returns>Id of the referenced element.</returns> | ||
| 3546 | private string ParseSimpleRefElement(XElement node, IntermediateSymbolDefinition symbolDefinition) | ||
| 3547 | { | ||
| 3548 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3549 | string id = null; | ||
| 3550 | |||
| 3551 | foreach (var attrib in node.Attributes()) | ||
| 3552 | { | ||
| 3553 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3554 | { | ||
| 3555 | switch (attrib.Name.LocalName) | ||
| 3556 | { | ||
| 3557 | case "Id": | ||
| 3558 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3559 | this.Core.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, id); | ||
| 3560 | break; | ||
| 3561 | default: | ||
| 3562 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3563 | break; | ||
| 3564 | } | ||
| 3565 | } | ||
| 3566 | else | ||
| 3567 | { | ||
| 3568 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3569 | } | ||
| 3570 | } | ||
| 3571 | |||
| 3572 | if (null == id) | ||
| 3573 | { | ||
| 3574 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3575 | } | ||
| 3576 | |||
| 3577 | this.Core.ParseForExtensionElements(node); | ||
| 3578 | |||
| 3579 | return id; | ||
| 3580 | } | ||
| 3581 | |||
| 3582 | /// <summary> | ||
| 3583 | /// Parses a PatchFamilyRef element. | ||
| 3584 | /// </summary> | ||
| 3585 | /// <param name="node">Element to parse.</param> | ||
| 3586 | /// <param name="parentType">The parent type.</param> | ||
| 3587 | /// <param name="parentId">The ID of the parent.</param> | ||
| 3588 | /// <returns>Id of the referenced element.</returns> | ||
| 3589 | private void ParsePatchFamilyRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 3590 | { | ||
| 3591 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3592 | var primaryKeys = new string[2]; | ||
| 3593 | |||
| 3594 | foreach (var attrib in node.Attributes()) | ||
| 3595 | { | ||
| 3596 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3597 | { | ||
| 3598 | switch (attrib.Name.LocalName) | ||
| 3599 | { | ||
| 3600 | case "Id": | ||
| 3601 | primaryKeys[0] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3602 | break; | ||
| 3603 | case "ProductCode": | ||
| 3604 | primaryKeys[1] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3605 | break; | ||
| 3606 | default: | ||
| 3607 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3608 | break; | ||
| 3609 | } | ||
| 3610 | } | ||
| 3611 | else | ||
| 3612 | { | ||
| 3613 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3614 | } | ||
| 3615 | } | ||
| 3616 | |||
| 3617 | if (null == primaryKeys[0]) | ||
| 3618 | { | ||
| 3619 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3620 | } | ||
| 3621 | |||
| 3622 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.MsiPatchSequence, primaryKeys); | ||
| 3623 | |||
| 3624 | this.Core.ParseForExtensionElements(node); | ||
| 3625 | |||
| 3626 | if (!this.Core.EncounteredError) | ||
| 3627 | { | ||
| 3628 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, primaryKeys[0], true); | ||
| 3629 | } | ||
| 3630 | } | ||
| 3631 | |||
| 3632 | /// <summary> | ||
| 3633 | /// Parses an ensure table element. | ||
| 3634 | /// </summary> | ||
| 3635 | /// <param name="node">Element to parse.</param> | ||
| 3636 | private void ParseEnsureTableElement(XElement node) | ||
| 3637 | { | ||
| 3638 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3639 | string id = null; | ||
| 3640 | |||
| 3641 | foreach (var attrib in node.Attributes()) | ||
| 3642 | { | ||
| 3643 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3644 | { | ||
| 3645 | switch (attrib.Name.LocalName) | ||
| 3646 | { | ||
| 3647 | case "Id": | ||
| 3648 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3649 | break; | ||
| 3650 | default: | ||
| 3651 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3652 | break; | ||
| 3653 | } | ||
| 3654 | } | ||
| 3655 | else | ||
| 3656 | { | ||
| 3657 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3658 | } | ||
| 3659 | } | ||
| 3660 | |||
| 3661 | if (null == id) | ||
| 3662 | { | ||
| 3663 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3664 | } | ||
| 3665 | else if (31 < id.Length) | ||
| 3666 | { | ||
| 3667 | this.Core.Write(ErrorMessages.TableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id)); | ||
| 3668 | } | ||
| 3669 | |||
| 3670 | this.Core.ParseForExtensionElements(node); | ||
| 3671 | |||
| 3672 | this.Core.EnsureTable(sourceLineNumbers, id); | ||
| 3673 | } | ||
| 3674 | |||
| 3675 | /// <summary> | ||
| 3676 | /// Parses a custom table element. | ||
| 3677 | /// </summary> | ||
| 3678 | /// <param name="node">Element to parse.</param> | ||
| 3679 | /// <remarks>not cleaned</remarks> | ||
| 3680 | private void ParseCustomTableElement(XElement node) | ||
| 3681 | { | ||
| 3682 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3683 | string tableId = null; | ||
| 3684 | var unreal = false; | ||
| 3685 | var columns = new List<WixCustomTableColumnSymbol>(); | ||
| 3686 | var foundColumns = false; | ||
| 3687 | |||
| 3688 | foreach (var attrib in node.Attributes()) | ||
| 3689 | { | ||
| 3690 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3691 | { | ||
| 3692 | switch (attrib.Name.LocalName) | ||
| 3693 | { | ||
| 3694 | case "Id": | ||
| 3695 | tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3696 | break; | ||
| 3697 | case "Unreal": | ||
| 3698 | unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3699 | break; | ||
| 3700 | default: | ||
| 3701 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3702 | break; | ||
| 3703 | } | ||
| 3704 | } | ||
| 3705 | else | ||
| 3706 | { | ||
| 3707 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3708 | } | ||
| 3709 | } | ||
| 3710 | |||
| 3711 | if (null == tableId) | ||
| 3712 | { | ||
| 3713 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3714 | } | ||
| 3715 | else if (31 < tableId.Length) | ||
| 3716 | { | ||
| 3717 | this.Core.Write(ErrorMessages.CustomTableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", tableId)); | ||
| 3718 | } | ||
| 3719 | |||
| 3720 | foreach (var child in node.Elements()) | ||
| 3721 | { | ||
| 3722 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 3723 | { | ||
| 3724 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 3725 | switch (child.Name.LocalName) | ||
| 3726 | { | ||
| 3727 | case "Column": | ||
| 3728 | foundColumns = true; | ||
| 3729 | |||
| 3730 | var column = this.ParseColumnElement(child, childSourceLineNumbers, tableId); | ||
| 3731 | if (column != null) | ||
| 3732 | { | ||
| 3733 | columns.Add(column); | ||
| 3734 | } | ||
| 3735 | break; | ||
| 3736 | case "Row": | ||
| 3737 | this.ParseRowElement(child, childSourceLineNumbers, tableId); | ||
| 3738 | break; | ||
| 3739 | default: | ||
| 3740 | this.Core.UnexpectedElement(node, child); | ||
| 3741 | break; | ||
| 3742 | } | ||
| 3743 | } | ||
| 3744 | else | ||
| 3745 | { | ||
| 3746 | this.Core.ParseExtensionElement(node, child); | ||
| 3747 | } | ||
| 3748 | } | ||
| 3749 | |||
| 3750 | if (columns.Count > 0) | ||
| 3751 | { | ||
| 3752 | if (!columns.Where(c => c.PrimaryKey).Any()) | ||
| 3753 | { | ||
| 3754 | this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); | ||
| 3755 | } | ||
| 3756 | |||
| 3757 | if (!this.Core.EncounteredError) | ||
| 3758 | { | ||
| 3759 | var columnNames = String.Join(new string(WixCustomTableSymbol.ColumnNamesSeparator, 1), columns.Select(c => c.Name)); | ||
| 3760 | |||
| 3761 | this.Core.AddSymbol(new WixCustomTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, tableId)) | ||
| 3762 | { | ||
| 3763 | ColumnNames = columnNames, | ||
| 3764 | Unreal = unreal, | ||
| 3765 | }); | ||
| 3766 | } | ||
| 3767 | else if (!foundColumns) | ||
| 3768 | { | ||
| 3769 | this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Column")); | ||
| 3770 | } | ||
| 3771 | } | ||
| 3772 | } | ||
| 3773 | |||
| 3774 | /// <summary> | ||
| 3775 | /// Parses a CustomTableRef element. | ||
| 3776 | /// </summary> | ||
| 3777 | /// <param name="node">Element to parse.</param> | ||
| 3778 | private void ParseCustomTableRefElement(XElement node) | ||
| 3779 | { | ||
| 3780 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3781 | string tableId = null; | ||
| 3782 | |||
| 3783 | foreach (var attrib in node.Attributes()) | ||
| 3784 | { | ||
| 3785 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3786 | { | ||
| 3787 | switch (attrib.Name.LocalName) | ||
| 3788 | { | ||
| 3789 | case "Id": | ||
| 3790 | tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3791 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); | ||
| 3792 | break; | ||
| 3793 | default: | ||
| 3794 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3795 | break; | ||
| 3796 | } | ||
| 3797 | } | ||
| 3798 | else | ||
| 3799 | { | ||
| 3800 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3801 | } | ||
| 3802 | } | ||
| 3803 | |||
| 3804 | if (null == tableId) | ||
| 3805 | { | ||
| 3806 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3807 | } | ||
| 3808 | |||
| 3809 | foreach (var child in node.Elements()) | ||
| 3810 | { | ||
| 3811 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 3812 | { | ||
| 3813 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 3814 | switch (child.Name.LocalName) | ||
| 3815 | { | ||
| 3816 | case "Row": | ||
| 3817 | this.ParseRowElement(child, childSourceLineNumbers, tableId); | ||
| 3818 | break; | ||
| 3819 | default: | ||
| 3820 | this.Core.UnexpectedElement(node, child); | ||
| 3821 | break; | ||
| 3822 | } | ||
| 3823 | } | ||
| 3824 | else | ||
| 3825 | { | ||
| 3826 | this.Core.ParseExtensionElement(node, child); | ||
| 3827 | } | ||
| 3828 | } | ||
| 3829 | } | ||
| 3830 | |||
| 3831 | /// <summary> | ||
| 3832 | /// Parses a Column element. | ||
| 3833 | /// </summary> | ||
| 3834 | /// <param name="child">Element to parse.</param> | ||
| 3835 | /// <param name="childSourceLineNumbers">Element's SourceLineNumbers.</param> | ||
| 3836 | /// <param name="tableId">Table Id.</param> | ||
| 3837 | private WixCustomTableColumnSymbol ParseColumnElement(XElement child, SourceLineNumber childSourceLineNumbers, string tableId) | ||
| 3838 | { | ||
| 3839 | string columnName = null; | ||
| 3840 | IntermediateFieldType? columnType = null; | ||
| 3841 | var description = String.Empty; | ||
| 3842 | int? keyColumn = null; | ||
| 3843 | var keyTable = String.Empty; | ||
| 3844 | var localizable = false; | ||
| 3845 | long? maxValue = null; | ||
| 3846 | long? minValue = null; | ||
| 3847 | WixCustomTableColumnCategoryType? category = null; | ||
| 3848 | var modularization = WixCustomTableColumnModularizeType.None; | ||
| 3849 | var nullable = false; | ||
| 3850 | var primaryKey = false; | ||
| 3851 | var setValues = String.Empty; | ||
| 3852 | var columnUnreal = false; | ||
| 3853 | var width = 0; | ||
| 3854 | |||
| 3855 | foreach (var childAttrib in child.Attributes()) | ||
| 3856 | { | ||
| 3857 | switch (childAttrib.Name.LocalName) | ||
| 3858 | { | ||
| 3859 | case "Id": | ||
| 3860 | columnName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib); | ||
| 3861 | break; | ||
| 3862 | case "Category": | ||
| 3863 | var categoryValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
| 3864 | switch (categoryValue) | ||
| 3865 | { | ||
| 3866 | case "text": | ||
| 3867 | category = WixCustomTableColumnCategoryType.Text; | ||
| 3868 | break; | ||
| 3869 | case "upperCase": | ||
| 3870 | category = WixCustomTableColumnCategoryType.UpperCase; | ||
| 3871 | break; | ||
| 3872 | case "lowerCase": | ||
| 3873 | category = WixCustomTableColumnCategoryType.LowerCase; | ||
| 3874 | break; | ||
| 3875 | case "integer": | ||
| 3876 | category = WixCustomTableColumnCategoryType.Integer; | ||
| 3877 | break; | ||
| 3878 | case "doubleInteger": | ||
| 3879 | category = WixCustomTableColumnCategoryType.DoubleInteger; | ||
| 3880 | break; | ||
| 3881 | case "timeDate": | ||
| 3882 | category = WixCustomTableColumnCategoryType.TimeDate; | ||
| 3883 | break; | ||
| 3884 | case "identifier": | ||
| 3885 | category = WixCustomTableColumnCategoryType.Identifier; | ||
| 3886 | break; | ||
| 3887 | case "property": | ||
| 3888 | category = WixCustomTableColumnCategoryType.Property; | ||
| 3889 | break; | ||
| 3890 | case "filename": | ||
| 3891 | category = WixCustomTableColumnCategoryType.Filename; | ||
| 3892 | break; | ||
| 3893 | case "wildCardFilename": | ||
| 3894 | category = WixCustomTableColumnCategoryType.WildCardFilename; | ||
| 3895 | break; | ||
| 3896 | case "path": | ||
| 3897 | category = WixCustomTableColumnCategoryType.Path; | ||
| 3898 | break; | ||
| 3899 | case "paths": | ||
| 3900 | category = WixCustomTableColumnCategoryType.Paths; | ||
| 3901 | break; | ||
| 3902 | case "anyPath": | ||
| 3903 | category = WixCustomTableColumnCategoryType.AnyPath; | ||
| 3904 | break; | ||
| 3905 | case "defaultDir": | ||
| 3906 | category = WixCustomTableColumnCategoryType.DefaultDir; | ||
| 3907 | break; | ||
| 3908 | case "regPath": | ||
| 3909 | category = WixCustomTableColumnCategoryType.RegPath; | ||
| 3910 | break; | ||
| 3911 | case "formatted": | ||
| 3912 | category = WixCustomTableColumnCategoryType.Formatted; | ||
| 3913 | break; | ||
| 3914 | case "formattedSddl": | ||
| 3915 | category = WixCustomTableColumnCategoryType.FormattedSddl; | ||
| 3916 | break; | ||
| 3917 | case "template": | ||
| 3918 | category = WixCustomTableColumnCategoryType.Template; | ||
| 3919 | break; | ||
| 3920 | case "condition": | ||
| 3921 | category = WixCustomTableColumnCategoryType.Condition; | ||
| 3922 | break; | ||
| 3923 | case "guid": | ||
| 3924 | category = WixCustomTableColumnCategoryType.Guid; | ||
| 3925 | break; | ||
| 3926 | case "version": | ||
| 3927 | category = WixCustomTableColumnCategoryType.Version; | ||
| 3928 | break; | ||
| 3929 | case "language": | ||
| 3930 | category = WixCustomTableColumnCategoryType.Language; | ||
| 3931 | break; | ||
| 3932 | case "binary": | ||
| 3933 | category = WixCustomTableColumnCategoryType.Binary; | ||
| 3934 | break; | ||
| 3935 | case "customSource": | ||
| 3936 | category = WixCustomTableColumnCategoryType.CustomSource; | ||
| 3937 | break; | ||
| 3938 | case "cabinet": | ||
| 3939 | category = WixCustomTableColumnCategoryType.Cabinet; | ||
| 3940 | break; | ||
| 3941 | case "shortcut": | ||
| 3942 | category = WixCustomTableColumnCategoryType.Shortcut; | ||
| 3943 | break; | ||
| 3944 | case "": | ||
| 3945 | break; | ||
| 3946 | default: | ||
| 3947 | this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Category", categoryValue, | ||
| 3948 | "text", "upperCase", "lowerCase", "integer", "doubleInteger", "timeDate", "identifier", "property", "filename", | ||
| 3949 | "wildCardFilename", "path", "paths", "anyPath", "defaultDir", "regPath", "formatted", "formattedSddl", "template", | ||
| 3950 | "condition", "guid", "version", "language", "binary", "customSource", "cabinet", "shortcut")); | ||
| 3951 | columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. | ||
| 3952 | break; | ||
| 3953 | } | ||
| 3954 | break; | ||
| 3955 | case "Description": | ||
| 3956 | description = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
| 3957 | break; | ||
| 3958 | case "KeyColumn": | ||
| 3959 | keyColumn = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 1, 32); | ||
| 3960 | break; | ||
| 3961 | case "KeyTable": | ||
| 3962 | keyTable = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
| 3963 | break; | ||
| 3964 | case "Localizable": | ||
| 3965 | localizable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); | ||
| 3966 | break; | ||
| 3967 | case "MaxValue": | ||
| 3968 | maxValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); | ||
| 3969 | break; | ||
| 3970 | case "MinValue": | ||
| 3971 | minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); | ||
| 3972 | break; | ||
| 3973 | case "Modularize": | ||
| 3974 | var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
| 3975 | switch (modularizeValue) | ||
| 3976 | { | ||
| 3977 | case "column": | ||
| 3978 | modularization = WixCustomTableColumnModularizeType.Column; | ||
| 3979 | break; | ||
| 3980 | case "companionFile": | ||
| 3981 | modularization = WixCustomTableColumnModularizeType.CompanionFile; | ||
| 3982 | break; | ||
| 3983 | case "condition": | ||
| 3984 | modularization = WixCustomTableColumnModularizeType.Condition; | ||
| 3985 | break; | ||
| 3986 | case "controlEventArgument": | ||
| 3987 | modularization = WixCustomTableColumnModularizeType.ControlEventArgument; | ||
| 3988 | break; | ||
| 3989 | case "controlText": | ||
| 3990 | modularization = WixCustomTableColumnModularizeType.ControlText; | ||
| 3991 | break; | ||
| 3992 | case "icon": | ||
| 3993 | modularization = WixCustomTableColumnModularizeType.Icon; | ||
| 3994 | break; | ||
| 3995 | case "none": | ||
| 3996 | modularization = WixCustomTableColumnModularizeType.None; | ||
| 3997 | break; | ||
| 3998 | case "property": | ||
| 3999 | modularization = WixCustomTableColumnModularizeType.Property; | ||
| 4000 | break; | ||
| 4001 | case "semicolonDelimited": | ||
| 4002 | modularization = WixCustomTableColumnModularizeType.SemicolonDelimited; | ||
| 4003 | break; | ||
| 4004 | case "": | ||
| 4005 | break; | ||
| 4006 | default: | ||
| 4007 | this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited")); | ||
| 4008 | columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. | ||
| 4009 | break; | ||
| 4010 | } | ||
| 4011 | break; | ||
| 4012 | case "Nullable": | ||
| 4013 | nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); | ||
| 4014 | break; | ||
| 4015 | case "PrimaryKey": | ||
| 4016 | primaryKey = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); | ||
| 4017 | break; | ||
| 4018 | case "Set": | ||
| 4019 | setValues = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
| 4020 | break; | ||
| 4021 | case "Type": | ||
| 4022 | var typeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
| 4023 | switch (typeValue) | ||
| 4024 | { | ||
| 4025 | case "binary": | ||
| 4026 | columnType = IntermediateFieldType.Path; | ||
| 4027 | break; | ||
| 4028 | case "int": | ||
| 4029 | columnType = IntermediateFieldType.Number; | ||
| 4030 | break; | ||
| 4031 | case "string": | ||
| 4032 | columnType = IntermediateFieldType.String; | ||
| 4033 | break; | ||
| 4034 | case "": | ||
| 4035 | break; | ||
| 4036 | default: | ||
| 4037 | this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); | ||
| 4038 | columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. | ||
| 4039 | break; | ||
| 4040 | } | ||
| 4041 | break; | ||
| 4042 | case "Width": | ||
| 4043 | width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); | ||
| 4044 | break; | ||
| 4045 | case "Unreal": | ||
| 4046 | columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); | ||
| 4047 | break; | ||
| 4048 | default: | ||
| 4049 | this.Core.UnexpectedAttribute(child, childAttrib); | ||
| 4050 | break; | ||
| 4051 | } | ||
| 4052 | } | ||
| 4053 | |||
| 4054 | if (null == columnName) | ||
| 4055 | { | ||
| 4056 | this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); | ||
| 4057 | } | ||
| 4058 | |||
| 4059 | if (!columnType.HasValue) | ||
| 4060 | { | ||
| 4061 | this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); | ||
| 4062 | } | ||
| 4063 | else if (columnType == IntermediateFieldType.Number) | ||
| 4064 | { | ||
| 4065 | if (2 != width && 4 != width) | ||
| 4066 | { | ||
| 4067 | this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); | ||
| 4068 | } | ||
| 4069 | } | ||
| 4070 | else if (columnType == IntermediateFieldType.Path) | ||
| 4071 | { | ||
| 4072 | if (!category.HasValue) | ||
| 4073 | { | ||
| 4074 | category = WixCustomTableColumnCategoryType.Binary; | ||
| 4075 | } | ||
| 4076 | else if (category != WixCustomTableColumnCategoryType.Binary) | ||
| 4077 | { | ||
| 4078 | this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); | ||
| 4079 | } | ||
| 4080 | } | ||
| 4081 | |||
| 4082 | this.Core.ParseForExtensionElements(child); | ||
| 4083 | |||
| 4084 | if (this.Core.EncounteredError) | ||
| 4085 | { | ||
| 4086 | return null; | ||
| 4087 | } | ||
| 4088 | |||
| 4089 | var attributes = primaryKey ? WixCustomTableColumnSymbolAttributes.PrimaryKey : WixCustomTableColumnSymbolAttributes.None; | ||
| 4090 | attributes |= localizable ? WixCustomTableColumnSymbolAttributes.Localizable : WixCustomTableColumnSymbolAttributes.None; | ||
| 4091 | attributes |= nullable ? WixCustomTableColumnSymbolAttributes.Nullable : WixCustomTableColumnSymbolAttributes.None; | ||
| 4092 | attributes |= columnUnreal ? WixCustomTableColumnSymbolAttributes.Unreal : WixCustomTableColumnSymbolAttributes.None; | ||
| 4093 | |||
| 4094 | var column = this.Core.AddSymbol(new WixCustomTableColumnSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, columnName)) | ||
| 4095 | { | ||
| 4096 | TableRef = tableId, | ||
| 4097 | Name = columnName, | ||
| 4098 | Type = columnType.Value, | ||
| 4099 | Attributes = attributes, | ||
| 4100 | Width = width, | ||
| 4101 | Category = category, | ||
| 4102 | Description = description, | ||
| 4103 | KeyColumn = keyColumn, | ||
| 4104 | KeyTable = keyTable, | ||
| 4105 | MaxValue = maxValue, | ||
| 4106 | MinValue = minValue, | ||
| 4107 | Modularize = modularization, | ||
| 4108 | Set = setValues, | ||
| 4109 | }); | ||
| 4110 | return column; | ||
| 4111 | } | ||
| 4112 | |||
| 4113 | /// <summary> | ||
| 4114 | /// Parses a Row element. | ||
| 4115 | /// </summary> | ||
| 4116 | /// <param name="node">Element to parse.</param> | ||
| 4117 | /// <param name="sourceLineNumbers">Element's SourceLineNumbers.</param> | ||
| 4118 | /// <param name="tableId">Table Id.</param> | ||
| 4119 | private void ParseRowElement(XElement node, SourceLineNumber sourceLineNumbers, string tableId) | ||
| 4120 | { | ||
| 4121 | var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant(); | ||
| 4122 | |||
| 4123 | foreach (var attrib in node.Attributes()) | ||
| 4124 | { | ||
| 4125 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4126 | } | ||
| 4127 | |||
| 4128 | foreach (var child in node.Elements()) | ||
| 4129 | { | ||
| 4130 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 4131 | switch (child.Name.LocalName) | ||
| 4132 | { | ||
| 4133 | case "Data": | ||
| 4134 | string columnName = null; | ||
| 4135 | string data = null; | ||
| 4136 | foreach (var attrib in child.Attributes()) | ||
| 4137 | { | ||
| 4138 | switch (attrib.Name.LocalName) | ||
| 4139 | { | ||
| 4140 | case "Column": | ||
| 4141 | columnName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); | ||
| 4142 | break; | ||
| 4143 | case "Value": | ||
| 4144 | data = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); | ||
| 4145 | break; | ||
| 4146 | default: | ||
| 4147 | this.Core.ParseExtensionAttribute(child, attrib); | ||
| 4148 | break; | ||
| 4149 | } | ||
| 4150 | } | ||
| 4151 | |||
| 4152 | this.Core.InnerTextDisallowed(node); | ||
| 4153 | |||
| 4154 | if (null == columnName) | ||
| 4155 | { | ||
| 4156 | this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column")); | ||
| 4157 | } | ||
| 4158 | |||
| 4159 | if (!this.Core.EncounteredError) | ||
| 4160 | { | ||
| 4161 | this.Core.AddSymbol(new WixCustomTableCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, rowId, columnName)) | ||
| 4162 | { | ||
| 4163 | RowId = rowId, | ||
| 4164 | ColumnRef = columnName, | ||
| 4165 | TableRef = tableId, | ||
| 4166 | Data = data | ||
| 4167 | }); | ||
| 4168 | } | ||
| 4169 | break; | ||
| 4170 | default: | ||
| 4171 | this.Core.UnexpectedElement(node, child); | ||
| 4172 | break; | ||
| 4173 | } | ||
| 4174 | } | ||
| 4175 | |||
| 4176 | if (!this.Core.EncounteredError) | ||
| 4177 | { | ||
| 4178 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); | ||
| 4179 | } | ||
| 4180 | } | ||
| 4181 | |||
| 4182 | /// <summary> | ||
| 4183 | /// Parses a directory element. | ||
| 4184 | /// </summary> | ||
| 4185 | /// <param name="node">Element to parse.</param> | ||
| 4186 | /// <param name="parentId">Optional identifier of parent directory.</param> | ||
| 4187 | /// <param name="diskId">Disk id inherited from parent directory.</param> | ||
| 4188 | /// <param name="fileSource">Path to source file as of yet.</param> | ||
| 4189 | private void ParseDirectoryElement(XElement node, string parentId, int diskId, string fileSource) | ||
| 4190 | { | ||
| 4191 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4192 | Identifier id = null; | ||
| 4193 | string componentGuidGenerationSeed = null; | ||
| 4194 | var fileSourceAttribSet = false; | ||
| 4195 | XAttribute nameAttribute = null; | ||
| 4196 | var name = "."; // default to parent directory. | ||
| 4197 | string shortName = null; | ||
| 4198 | string sourceName = null; | ||
| 4199 | string shortSourceName = null; | ||
| 4200 | string symbols = null; | ||
| 4201 | |||
| 4202 | foreach (var attrib in node.Attributes()) | ||
| 4203 | { | ||
| 4204 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4205 | { | ||
| 4206 | switch (attrib.Name.LocalName) | ||
| 4207 | { | ||
| 4208 | case "Id": | ||
| 4209 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 4210 | break; | ||
| 4211 | case "ComponentGuidGenerationSeed": | ||
| 4212 | componentGuidGenerationSeed = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 4213 | break; | ||
| 4214 | case "DiskId": | ||
| 4215 | diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 4216 | break; | ||
| 4217 | case "FileSource": | ||
| 4218 | fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4219 | fileSourceAttribSet = true; | ||
| 4220 | break; | ||
| 4221 | case "Name": | ||
| 4222 | if ("." == attrib.Value) | ||
| 4223 | { | ||
| 4224 | name = attrib.Value; | ||
| 4225 | } | ||
| 4226 | else | ||
| 4227 | { | ||
| 4228 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 4229 | } | ||
| 4230 | nameAttribute = attrib; | ||
| 4231 | break; | ||
| 4232 | case "ShortName": | ||
| 4233 | shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
| 4234 | break; | ||
| 4235 | case "ShortSourceName": | ||
| 4236 | shortSourceName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
| 4237 | break; | ||
| 4238 | case "SourceName": | ||
| 4239 | if ("." == attrib.Value) | ||
| 4240 | { | ||
| 4241 | sourceName = attrib.Value; | ||
| 4242 | } | ||
| 4243 | else | ||
| 4244 | { | ||
| 4245 | sourceName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 4246 | } | ||
| 4247 | break; | ||
| 4248 | default: | ||
| 4249 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4250 | break; | ||
| 4251 | } | ||
| 4252 | } | ||
| 4253 | else | ||
| 4254 | { | ||
| 4255 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4256 | } | ||
| 4257 | } | ||
| 4258 | |||
| 4259 | if (nameAttribute == null) | ||
| 4260 | { | ||
| 4261 | if (!String.IsNullOrEmpty(shortName)) | ||
| 4262 | { | ||
| 4263 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); | ||
| 4264 | } | ||
| 4265 | } | ||
| 4266 | else if (!String.IsNullOrEmpty(name)) | ||
| 4267 | { | ||
| 4268 | if (String.IsNullOrEmpty(shortName)) | ||
| 4269 | { | ||
| 4270 | } | ||
| 4271 | else if (name == ".") | ||
| 4272 | { | ||
| 4273 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name", name)); | ||
| 4274 | } | ||
| 4275 | else if (name.Equals(shortName, StringComparison.OrdinalIgnoreCase)) | ||
| 4276 | { | ||
| 4277 | this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "Name", "ShortName", name)); | ||
| 4278 | } | ||
| 4279 | } | ||
| 4280 | |||
| 4281 | if (String.IsNullOrEmpty(sourceName)) | ||
| 4282 | { | ||
| 4283 | if (!String.IsNullOrEmpty(shortSourceName)) | ||
| 4284 | { | ||
| 4285 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName")); | ||
| 4286 | } | ||
| 4287 | } | ||
| 4288 | else | ||
| 4289 | { | ||
| 4290 | if (String.IsNullOrEmpty(shortSourceName)) | ||
| 4291 | { | ||
| 4292 | } | ||
| 4293 | else if (sourceName == ".") | ||
| 4294 | { | ||
| 4295 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName", sourceName)); | ||
| 4296 | } | ||
| 4297 | else if (sourceName.Equals(shortSourceName, StringComparison.OrdinalIgnoreCase)) | ||
| 4298 | { | ||
| 4299 | this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "SourceName", "ShortSourceName", sourceName)); | ||
| 4300 | } | ||
| 4301 | } | ||
| 4302 | |||
| 4303 | if (null == id) | ||
| 4304 | { | ||
| 4305 | id = this.Core.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); | ||
| 4306 | } | ||
| 4307 | else if (WindowsInstallerStandard.IsStandardDirectory(id.Id)) | ||
| 4308 | { | ||
| 4309 | if (String.IsNullOrEmpty(sourceName)) | ||
| 4310 | { | ||
| 4311 | this.Core.Write(CompilerWarnings.DefiningStandardDirectoryDeprecated(sourceLineNumbers, id.Id)); | ||
| 4312 | } | ||
| 4313 | |||
| 4314 | if (id.Id == "TARGETDIR" && name != "SourceDir" && shortName == null && shortSourceName == null && sourceName == null) | ||
| 4315 | { | ||
| 4316 | this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name)); | ||
| 4317 | } | ||
| 4318 | } | ||
| 4319 | |||
| 4320 | // Update the file source path appropriately. | ||
| 4321 | if (fileSourceAttribSet) | ||
| 4322 | { | ||
| 4323 | if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) | ||
| 4324 | { | ||
| 4325 | fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); | ||
| 4326 | } | ||
| 4327 | } | ||
| 4328 | else // add the appropriate part of this directory element to the file source. | ||
| 4329 | { | ||
| 4330 | string append = String.IsNullOrEmpty(sourceName) ? name : sourceName; | ||
| 4331 | |||
| 4332 | if (!String.IsNullOrEmpty(append)) | ||
| 4333 | { | ||
| 4334 | fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar); | ||
| 4335 | } | ||
| 4336 | } | ||
| 4337 | |||
| 4338 | foreach (var child in node.Elements()) | ||
| 4339 | { | ||
| 4340 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 4341 | { | ||
| 4342 | switch (child.Name.LocalName) | ||
| 4343 | { | ||
| 4344 | case "Component": | ||
| 4345 | this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id.Id, fileSource); | ||
| 4346 | break; | ||
| 4347 | case "Directory": | ||
| 4348 | this.ParseDirectoryElement(child, id.Id, diskId, fileSource); | ||
| 4349 | break; | ||
| 4350 | case "Merge": | ||
| 4351 | this.ParseMergeElement(child, id.Id, diskId); | ||
| 4352 | break; | ||
| 4353 | case "SymbolPath": | ||
| 4354 | if (null != symbols) | ||
| 4355 | { | ||
| 4356 | symbols += ";" + this.ParseSymbolPathElement(child); | ||
| 4357 | } | ||
| 4358 | else | ||
| 4359 | { | ||
| 4360 | symbols = this.ParseSymbolPathElement(child); | ||
| 4361 | } | ||
| 4362 | break; | ||
| 4363 | default: | ||
| 4364 | this.Core.UnexpectedElement(node, child); | ||
| 4365 | break; | ||
| 4366 | } | ||
| 4367 | } | ||
| 4368 | else | ||
| 4369 | { | ||
| 4370 | this.Core.ParseExtensionElement(node, child); | ||
| 4371 | } | ||
| 4372 | } | ||
| 4373 | |||
| 4374 | if (!this.Core.EncounteredError) | ||
| 4375 | { | ||
| 4376 | this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) | ||
| 4377 | { | ||
| 4378 | ParentDirectoryRef = parentId, | ||
| 4379 | Name = name, | ||
| 4380 | ShortName = shortName, | ||
| 4381 | SourceName = sourceName, | ||
| 4382 | SourceShortName = shortSourceName, | ||
| 4383 | ComponentGuidGenerationSeed = componentGuidGenerationSeed | ||
| 4384 | }); | ||
| 4385 | |||
| 4386 | if (null != symbols) | ||
| 4387 | { | ||
| 4388 | this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, id) | ||
| 4389 | { | ||
| 4390 | SymbolType = SymbolPathType.Directory, | ||
| 4391 | SymbolId = id.Id, | ||
| 4392 | SymbolPaths = symbols, | ||
| 4393 | }); | ||
| 4394 | } | ||
| 4395 | } | ||
| 4396 | } | ||
| 4397 | |||
| 4398 | /// <summary> | ||
| 4399 | /// Parses a directory reference element. | ||
| 4400 | /// </summary> | ||
| 4401 | /// <param name="node">Element to parse.</param> | ||
| 4402 | private void ParseDirectoryRefElement(XElement node) | ||
| 4403 | { | ||
| 4404 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4405 | string id = null; | ||
| 4406 | var diskId = CompilerConstants.IntegerNotSet; | ||
| 4407 | var fileSource = String.Empty; | ||
| 4408 | |||
| 4409 | foreach (var attrib in node.Attributes()) | ||
| 4410 | { | ||
| 4411 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4412 | { | ||
| 4413 | switch (attrib.Name.LocalName) | ||
| 4414 | { | ||
| 4415 | case "Id": | ||
| 4416 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 4417 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); | ||
| 4418 | break; | ||
| 4419 | case "DiskId": | ||
| 4420 | diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 4421 | break; | ||
| 4422 | case "FileSource": | ||
| 4423 | fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4424 | break; | ||
| 4425 | default: | ||
| 4426 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4427 | break; | ||
| 4428 | } | ||
| 4429 | } | ||
| 4430 | else | ||
| 4431 | { | ||
| 4432 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4433 | } | ||
| 4434 | } | ||
| 4435 | |||
| 4436 | if (null == id) | ||
| 4437 | { | ||
| 4438 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 4439 | } | ||
| 4440 | else if (WindowsInstallerStandard.IsStandardDirectory(id)) | ||
| 4441 | { | ||
| 4442 | this.Core.Write(CompilerWarnings.DirectoryRefStandardDirectoryDeprecated(sourceLineNumbers, id)); | ||
| 4443 | } | ||
| 4444 | |||
| 4445 | if (!String.IsNullOrEmpty(fileSource) && !fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) | ||
| 4446 | { | ||
| 4447 | fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); | ||
| 4448 | } | ||
| 4449 | |||
| 4450 | foreach (var child in node.Elements()) | ||
| 4451 | { | ||
| 4452 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 4453 | { | ||
| 4454 | switch (child.Name.LocalName) | ||
| 4455 | { | ||
| 4456 | case "Component": | ||
| 4457 | this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id, fileSource); | ||
| 4458 | break; | ||
| 4459 | case "Directory": | ||
| 4460 | this.ParseDirectoryElement(child, id, diskId, fileSource); | ||
| 4461 | break; | ||
| 4462 | case "Merge": | ||
| 4463 | this.ParseMergeElement(child, id, diskId); | ||
| 4464 | break; | ||
| 4465 | default: | ||
| 4466 | this.Core.UnexpectedElement(node, child); | ||
| 4467 | break; | ||
| 4468 | } | ||
| 4469 | } | ||
| 4470 | else | ||
| 4471 | { | ||
| 4472 | this.Core.ParseExtensionElement(node, child); | ||
| 4473 | } | ||
| 4474 | } | ||
| 4475 | } | ||
| 4476 | |||
| 4477 | /// <summary> | ||
| 4478 | /// Parses a directory search element. | ||
| 4479 | /// </summary> | ||
| 4480 | /// <param name="node">Element to parse.</param> | ||
| 4481 | /// <param name="parentSignature">Signature of parent search element.</param> | ||
| 4482 | /// <returns>Signature of search element.</returns> | ||
| 4483 | private string ParseDirectorySearchElement(XElement node, string parentSignature) | ||
| 4484 | { | ||
| 4485 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4486 | Identifier id = null; | ||
| 4487 | var depth = CompilerConstants.IntegerNotSet; | ||
| 4488 | string path = null; | ||
| 4489 | var assignToProperty = false; | ||
| 4490 | string signature = null; | ||
| 4491 | |||
| 4492 | foreach (var attrib in node.Attributes()) | ||
| 4493 | { | ||
| 4494 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4495 | { | ||
| 4496 | switch (attrib.Name.LocalName) | ||
| 4497 | { | ||
| 4498 | case "Id": | ||
| 4499 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 4500 | break; | ||
| 4501 | case "Depth": | ||
| 4502 | depth = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 4503 | break; | ||
| 4504 | case "Path": | ||
| 4505 | path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4506 | break; | ||
| 4507 | case "AssignToProperty": | ||
| 4508 | assignToProperty = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4509 | break; | ||
| 4510 | default: | ||
| 4511 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4512 | break; | ||
| 4513 | } | ||
| 4514 | } | ||
| 4515 | else | ||
| 4516 | { | ||
| 4517 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4518 | } | ||
| 4519 | } | ||
| 4520 | |||
| 4521 | if (null == id) | ||
| 4522 | { | ||
| 4523 | id = this.Core.CreateIdentifier("dir", path, depth.ToString()); | ||
| 4524 | } | ||
| 4525 | |||
| 4526 | signature = id.Id; | ||
| 4527 | |||
| 4528 | var oneChild = false; | ||
| 4529 | var hasFileSearch = false; | ||
| 4530 | foreach (var child in node.Elements()) | ||
| 4531 | { | ||
| 4532 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 4533 | { | ||
| 4534 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 4535 | switch (child.Name.LocalName) | ||
| 4536 | { | ||
| 4537 | case "DirectorySearch": | ||
| 4538 | if (oneChild) | ||
| 4539 | { | ||
| 4540 | this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
| 4541 | } | ||
| 4542 | oneChild = true; | ||
| 4543 | signature = this.ParseDirectorySearchElement(child, id.Id); | ||
| 4544 | break; | ||
| 4545 | case "DirectorySearchRef": | ||
| 4546 | if (oneChild) | ||
| 4547 | { | ||
| 4548 | this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
| 4549 | } | ||
| 4550 | oneChild = true; | ||
| 4551 | signature = this.ParseDirectorySearchRefElement(child, id.Id); | ||
| 4552 | break; | ||
| 4553 | case "FileSearch": | ||
| 4554 | if (oneChild) | ||
| 4555 | { | ||
| 4556 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 4557 | } | ||
| 4558 | oneChild = true; | ||
| 4559 | hasFileSearch = true; | ||
| 4560 | signature = this.ParseFileSearchElement(child, id.Id, assignToProperty, depth); | ||
| 4561 | break; | ||
| 4562 | case "FileSearchRef": | ||
| 4563 | if (oneChild) | ||
| 4564 | { | ||
| 4565 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 4566 | } | ||
| 4567 | oneChild = true; | ||
| 4568 | signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); | ||
| 4569 | break; | ||
| 4570 | default: | ||
| 4571 | this.Core.UnexpectedElement(node, child); | ||
| 4572 | break; | ||
| 4573 | } | ||
| 4574 | |||
| 4575 | // If AssignToProperty is set, only a FileSearch | ||
| 4576 | // or no child element can be nested. | ||
| 4577 | if (assignToProperty) | ||
| 4578 | { | ||
| 4579 | if (!hasFileSearch) | ||
| 4580 | { | ||
| 4581 | this.Core.Write(ErrorMessages.IllegalParentAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AssignToProperty", child.Name.LocalName)); | ||
| 4582 | } | ||
| 4583 | else if (!oneChild) | ||
| 4584 | { | ||
| 4585 | // This a normal directory search. | ||
| 4586 | assignToProperty = false; | ||
| 4587 | } | ||
| 4588 | } | ||
| 4589 | } | ||
| 4590 | else | ||
| 4591 | { | ||
| 4592 | this.Core.ParseExtensionElement(node, child); | ||
| 4593 | } | ||
| 4594 | } | ||
| 4595 | |||
| 4596 | if (!this.Core.EncounteredError) | ||
| 4597 | { | ||
| 4598 | var access = id.Access; | ||
| 4599 | var rowId = id.Id; | ||
| 4600 | |||
| 4601 | // If AssignToProperty is set, the DrLocator row created by | ||
| 4602 | // ParseFileSearchElement creates the directory entry to return | ||
| 4603 | // and the row created here is for the file search. | ||
| 4604 | if (assignToProperty) | ||
| 4605 | { | ||
| 4606 | access = AccessModifier.Section; | ||
| 4607 | rowId = signature; | ||
| 4608 | |||
| 4609 | // The property should be set to the directory search Id. | ||
| 4610 | signature = id.Id; | ||
| 4611 | } | ||
| 4612 | |||
| 4613 | var symbol = this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(access, rowId, parentSignature, path)) | ||
| 4614 | { | ||
| 4615 | SignatureRef = rowId, | ||
| 4616 | Parent = parentSignature, | ||
| 4617 | Path = path, | ||
| 4618 | }); | ||
| 4619 | |||
| 4620 | if (CompilerConstants.IntegerNotSet != depth) | ||
| 4621 | { | ||
| 4622 | symbol.Depth = depth; | ||
| 4623 | } | ||
| 4624 | } | ||
| 4625 | |||
| 4626 | return signature; | ||
| 4627 | } | ||
| 4628 | |||
| 4629 | /// <summary> | ||
| 4630 | /// Parses a directory search reference element. | ||
| 4631 | /// </summary> | ||
| 4632 | /// <param name="node">Element to parse.</param> | ||
| 4633 | /// <param name="parentSignature">Signature of parent search element.</param> | ||
| 4634 | /// <returns>Signature of search element.</returns> | ||
| 4635 | private string ParseDirectorySearchRefElement(XElement node, string parentSignature) | ||
| 4636 | { | ||
| 4637 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4638 | Identifier id = null; | ||
| 4639 | Identifier parent = null; | ||
| 4640 | string path = null; | ||
| 4641 | string signature = null; | ||
| 4642 | |||
| 4643 | foreach (var attrib in node.Attributes()) | ||
| 4644 | { | ||
| 4645 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4646 | { | ||
| 4647 | switch (attrib.Name.LocalName) | ||
| 4648 | { | ||
| 4649 | case "Id": | ||
| 4650 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 4651 | break; | ||
| 4652 | case "Parent": | ||
| 4653 | parent = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 4654 | break; | ||
| 4655 | case "Path": | ||
| 4656 | path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4657 | break; | ||
| 4658 | default: | ||
| 4659 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4660 | break; | ||
| 4661 | } | ||
| 4662 | } | ||
| 4663 | else | ||
| 4664 | { | ||
| 4665 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4666 | } | ||
| 4667 | } | ||
| 4668 | |||
| 4669 | if (null != parent) | ||
| 4670 | { | ||
| 4671 | if (!String.IsNullOrEmpty(parentSignature)) | ||
| 4672 | { | ||
| 4673 | this.Core.Write(ErrorMessages.CanNotHaveTwoParents(sourceLineNumbers, id.Id, parent.Id, parentSignature)); | ||
| 4674 | } | ||
| 4675 | else | ||
| 4676 | { | ||
| 4677 | parentSignature = parent.Id; | ||
| 4678 | } | ||
| 4679 | } | ||
| 4680 | |||
| 4681 | if (null == id) | ||
| 4682 | { | ||
| 4683 | id = this.Core.CreateIdentifier("dsr", parentSignature, path); | ||
| 4684 | } | ||
| 4685 | |||
| 4686 | signature = id.Id; | ||
| 4687 | |||
| 4688 | var oneChild = false; | ||
| 4689 | foreach (var child in node.Elements()) | ||
| 4690 | { | ||
| 4691 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 4692 | { | ||
| 4693 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 4694 | switch (child.Name.LocalName) | ||
| 4695 | { | ||
| 4696 | case "DirectorySearch": | ||
| 4697 | if (oneChild) | ||
| 4698 | { | ||
| 4699 | this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
| 4700 | } | ||
| 4701 | oneChild = true; | ||
| 4702 | signature = this.ParseDirectorySearchElement(child, id.Id); | ||
| 4703 | break; | ||
| 4704 | case "DirectorySearchRef": | ||
| 4705 | if (oneChild) | ||
| 4706 | { | ||
| 4707 | this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
| 4708 | } | ||
| 4709 | oneChild = true; | ||
| 4710 | signature = this.ParseDirectorySearchRefElement(child, id.Id); | ||
| 4711 | break; | ||
| 4712 | case "FileSearch": | ||
| 4713 | if (oneChild) | ||
| 4714 | { | ||
| 4715 | this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
| 4716 | } | ||
| 4717 | oneChild = true; | ||
| 4718 | signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); | ||
| 4719 | break; | ||
| 4720 | case "FileSearchRef": | ||
| 4721 | if (oneChild) | ||
| 4722 | { | ||
| 4723 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 4724 | } | ||
| 4725 | oneChild = true; | ||
| 4726 | signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); | ||
| 4727 | break; | ||
| 4728 | default: | ||
| 4729 | this.Core.UnexpectedElement(node, child); | ||
| 4730 | break; | ||
| 4731 | } | ||
| 4732 | } | ||
| 4733 | else | ||
| 4734 | { | ||
| 4735 | this.Core.ParseExtensionElement(node, child); | ||
| 4736 | } | ||
| 4737 | } | ||
| 4738 | |||
| 4739 | |||
| 4740 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.DrLocator, id.Id, parentSignature, path); | ||
| 4741 | |||
| 4742 | return signature; | ||
| 4743 | } | ||
| 4744 | |||
| 4745 | /// <summary> | ||
| 4746 | /// Parses a feature element. | ||
| 4747 | /// </summary> | ||
| 4748 | /// <param name="node">Element to parse.</param> | ||
| 4749 | /// <param name="parentType">The type of parent.</param> | ||
| 4750 | /// <param name="parentId">Optional identifer for parent feature.</param> | ||
| 4751 | /// <param name="lastDisplay">Display value for last feature used to get the features to display in the same order as specified | ||
| 4752 | /// in the source code.</param> | ||
| 4753 | private void ParseFeatureElement(XElement node, ComplexReferenceParentType parentType, string parentId, ref int lastDisplay) | ||
| 4754 | { | ||
| 4755 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4756 | Identifier id = null; | ||
| 4757 | string configurableDirectory = null; | ||
| 4758 | string description = null; | ||
| 4759 | var displayValue = "collapse"; | ||
| 4760 | var level = 1; | ||
| 4761 | string title = null; | ||
| 4762 | |||
| 4763 | var installDefault = FeatureInstallDefault.Local; | ||
| 4764 | var typicalDefault = FeatureTypicalDefault.Install; | ||
| 4765 | var disallowAbsent = false; | ||
| 4766 | var disallowAdvertise = false; | ||
| 4767 | var display = 0; | ||
| 4768 | |||
| 4769 | foreach (var attrib in node.Attributes()) | ||
| 4770 | { | ||
| 4771 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4772 | { | ||
| 4773 | switch (attrib.Name.LocalName) | ||
| 4774 | { | ||
| 4775 | case "Id": | ||
| 4776 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 4777 | break; | ||
| 4778 | case "AllowAbsent": | ||
| 4779 | disallowAbsent = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); | ||
| 4780 | break; | ||
| 4781 | case "AllowAdvertise": | ||
| 4782 | disallowAdvertise = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); | ||
| 4783 | break; | ||
| 4784 | case "ConfigurableDirectory": | ||
| 4785 | configurableDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 4786 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, configurableDirectory); | ||
| 4787 | break; | ||
| 4788 | case "Description": | ||
| 4789 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4790 | break; | ||
| 4791 | case "Display": | ||
| 4792 | displayValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4793 | break; | ||
| 4794 | case "InstallDefault": | ||
| 4795 | var installDefaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4796 | switch (installDefaultValue) | ||
| 4797 | { | ||
| 4798 | case "followParent": | ||
| 4799 | if (ComplexReferenceParentType.Product == parentType) | ||
| 4800 | { | ||
| 4801 | this.Core.Write(ErrorMessages.RootFeatureCannotFollowParent(sourceLineNumbers)); | ||
| 4802 | } | ||
| 4803 | //bits = bits | MsiInterop.MsidbFeatureAttributesFollowParent; | ||
| 4804 | installDefault = FeatureInstallDefault.FollowParent; | ||
| 4805 | break; | ||
| 4806 | case "local": // this is the default | ||
| 4807 | installDefault = FeatureInstallDefault.Local; | ||
| 4808 | break; | ||
| 4809 | case "source": | ||
| 4810 | //bits = bits | MsiInterop.MsidbFeatureAttributesFavorSource; | ||
| 4811 | installDefault = FeatureInstallDefault.Source; | ||
| 4812 | break; | ||
| 4813 | case "": | ||
| 4814 | break; | ||
| 4815 | default: | ||
| 4816 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installDefaultValue, "followParent", "local", "source")); | ||
| 4817 | break; | ||
| 4818 | } | ||
| 4819 | break; | ||
| 4820 | case "Level": | ||
| 4821 | level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 4822 | break; | ||
| 4823 | case "Title": | ||
| 4824 | title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4825 | if ("PUT-FEATURE-TITLE-HERE" == title) | ||
| 4826 | { | ||
| 4827 | this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, title)); | ||
| 4828 | } | ||
| 4829 | break; | ||
| 4830 | case "TypicalDefault": | ||
| 4831 | var typicalValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4832 | switch (typicalValue) | ||
| 4833 | { | ||
| 4834 | case "advertise": | ||
| 4835 | //bits |= MsiInterop.MsidbFeatureAttributesFavorAdvertise; | ||
| 4836 | typicalDefault = FeatureTypicalDefault.Advertise; | ||
| 4837 | break; | ||
| 4838 | case "install": // this is the default | ||
| 4839 | typicalDefault = FeatureTypicalDefault.Install; | ||
| 4840 | break; | ||
| 4841 | default: | ||
| 4842 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typicalValue, "advertise", "install")); | ||
| 4843 | break; | ||
| 4844 | } | ||
| 4845 | break; | ||
| 4846 | default: | ||
| 4847 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4848 | break; | ||
| 4849 | } | ||
| 4850 | } | ||
| 4851 | else | ||
| 4852 | { | ||
| 4853 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4854 | } | ||
| 4855 | } | ||
| 4856 | |||
| 4857 | if (null == id) | ||
| 4858 | { | ||
| 4859 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 4860 | id = Identifier.Invalid; | ||
| 4861 | } | ||
| 4862 | else if (38 < id.Id.Length) | ||
| 4863 | { | ||
| 4864 | this.Core.Write(ErrorMessages.FeatureNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
| 4865 | } | ||
| 4866 | |||
| 4867 | if (null != configurableDirectory && configurableDirectory.ToUpper(CultureInfo.InvariantCulture) != configurableDirectory) | ||
| 4868 | { | ||
| 4869 | this.Core.Write(ErrorMessages.FeatureConfigurableDirectoryNotUppercase(sourceLineNumbers, node.Name.LocalName, "ConfigurableDirectory", configurableDirectory)); | ||
| 4870 | } | ||
| 4871 | |||
| 4872 | if (FeatureTypicalDefault.Advertise == typicalDefault && disallowAdvertise) | ||
| 4873 | { | ||
| 4874 | this.Core.Write(ErrorMessages.FeatureCannotFavorAndDisallowAdvertise(sourceLineNumbers, node.Name.LocalName, "TypicalDefault", "advertise", "AllowAdvertise", "no")); | ||
| 4875 | } | ||
| 4876 | |||
| 4877 | var childDisplay = 0; | ||
| 4878 | foreach (var child in node.Elements()) | ||
| 4879 | { | ||
| 4880 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 4881 | { | ||
| 4882 | switch (child.Name.LocalName) | ||
| 4883 | { | ||
| 4884 | case "ComponentGroupRef": | ||
| 4885 | this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); | ||
| 4886 | break; | ||
| 4887 | case "ComponentRef": | ||
| 4888 | this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); | ||
| 4889 | break; | ||
| 4890 | case "Component": | ||
| 4891 | this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id.Id, null, CompilerConstants.IntegerNotSet, null, null); | ||
| 4892 | break; | ||
| 4893 | case "Feature": | ||
| 4894 | this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id.Id, ref childDisplay); | ||
| 4895 | break; | ||
| 4896 | case "FeatureGroupRef": | ||
| 4897 | this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id); | ||
| 4898 | break; | ||
| 4899 | case "FeatureRef": | ||
| 4900 | this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id.Id); | ||
| 4901 | break; | ||
| 4902 | case "Level": | ||
| 4903 | this.ParseLevelElement(child, id.Id); | ||
| 4904 | break; | ||
| 4905 | case "MergeRef": | ||
| 4906 | this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id.Id); | ||
| 4907 | break; | ||
| 4908 | default: | ||
| 4909 | this.Core.UnexpectedElement(node, child); | ||
| 4910 | break; | ||
| 4911 | } | ||
| 4912 | } | ||
| 4913 | else | ||
| 4914 | { | ||
| 4915 | this.Core.ParseExtensionElement(node, child); | ||
| 4916 | } | ||
| 4917 | } | ||
| 4918 | |||
| 4919 | switch (displayValue) | ||
| 4920 | { | ||
| 4921 | case "collapse": | ||
| 4922 | lastDisplay = (lastDisplay | 1) + 1; | ||
| 4923 | display = lastDisplay; | ||
| 4924 | break; | ||
| 4925 | case "expand": | ||
| 4926 | lastDisplay = (lastDisplay + 1) | 1; | ||
| 4927 | display = lastDisplay; | ||
| 4928 | break; | ||
| 4929 | case "hidden": | ||
| 4930 | display = 0; | ||
| 4931 | break; | ||
| 4932 | default: | ||
| 4933 | if (!Int32.TryParse(displayValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out display)) | ||
| 4934 | { | ||
| 4935 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Display", displayValue, "collapse", "expand", "hidden")); | ||
| 4936 | } | ||
| 4937 | else | ||
| 4938 | { | ||
| 4939 | // Save the display value (if its not hidden) for subsequent rows | ||
| 4940 | if (0 != display) | ||
| 4941 | { | ||
| 4942 | lastDisplay = display; | ||
| 4943 | } | ||
| 4944 | } | ||
| 4945 | break; | ||
| 4946 | } | ||
| 4947 | |||
| 4948 | if (!this.Core.EncounteredError) | ||
| 4949 | { | ||
| 4950 | this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, id) | ||
| 4951 | { | ||
| 4952 | ParentFeatureRef = null, // this field is set in the linker | ||
| 4953 | Title = title, | ||
| 4954 | Description = description, | ||
| 4955 | Display = display, | ||
| 4956 | Level = level, | ||
| 4957 | DirectoryRef = configurableDirectory, | ||
| 4958 | DisallowAbsent = disallowAbsent, | ||
| 4959 | DisallowAdvertise = disallowAdvertise, | ||
| 4960 | InstallDefault = installDefault, | ||
| 4961 | TypicalDefault = typicalDefault, | ||
| 4962 | }); | ||
| 4963 | |||
| 4964 | if (ComplexReferenceParentType.Unknown != parentType) | ||
| 4965 | { | ||
| 4966 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id.Id, false); | ||
| 4967 | } | ||
| 4968 | } | ||
| 4969 | } | ||
| 4970 | |||
| 4971 | /// <summary> | ||
| 4972 | /// Parses a feature reference element. | ||
| 4973 | /// </summary> | ||
| 4974 | /// <param name="node">Element to parse.</param> | ||
| 4975 | /// <param name="parentType">The type of parent.</param> | ||
| 4976 | /// <param name="parentId">Optional identifier for parent feature.</param> | ||
| 4977 | private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 4978 | { | ||
| 4979 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4980 | string id = null; | ||
| 4981 | var ignoreParent = YesNoType.NotSet; | ||
| 4982 | |||
| 4983 | foreach (var attrib in node.Attributes()) | ||
| 4984 | { | ||
| 4985 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4986 | { | ||
| 4987 | switch (attrib.Name.LocalName) | ||
| 4988 | { | ||
| 4989 | case "Id": | ||
| 4990 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 4991 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, id); | ||
| 4992 | break; | ||
| 4993 | case "IgnoreParent": | ||
| 4994 | ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4995 | break; | ||
| 4996 | default: | ||
| 4997 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4998 | break; | ||
| 4999 | } | ||
| 5000 | } | ||
| 5001 | else | ||
| 5002 | { | ||
| 5003 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 5004 | } | ||
| 5005 | } | ||
| 5006 | |||
| 5007 | |||
| 5008 | if (null == id) | ||
| 5009 | { | ||
| 5010 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 5011 | } | ||
| 5012 | |||
| 5013 | var lastDisplay = 0; | ||
| 5014 | foreach (var child in node.Elements()) | ||
| 5015 | { | ||
| 5016 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 5017 | { | ||
| 5018 | switch (child.Name.LocalName) | ||
| 5019 | { | ||
| 5020 | case "ComponentGroupRef": | ||
| 5021 | this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id, null); | ||
| 5022 | break; | ||
| 5023 | case "ComponentRef": | ||
| 5024 | this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id, null); | ||
| 5025 | break; | ||
| 5026 | case "Component": | ||
| 5027 | this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id, null, CompilerConstants.IntegerNotSet, null, null); | ||
| 5028 | break; | ||
| 5029 | case "Feature": | ||
| 5030 | this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id, ref lastDisplay); | ||
| 5031 | break; | ||
| 5032 | case "FeatureGroup": | ||
| 5033 | this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Feature, id); | ||
| 5034 | break; | ||
| 5035 | case "FeatureGroupRef": | ||
| 5036 | this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id); | ||
| 5037 | break; | ||
| 5038 | case "FeatureRef": | ||
| 5039 | this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id); | ||
| 5040 | break; | ||
| 5041 | case "MergeRef": | ||
| 5042 | this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id); | ||
| 5043 | break; | ||
| 5044 | default: | ||
| 5045 | this.Core.UnexpectedElement(node, child); | ||
| 5046 | break; | ||
| 5047 | } | ||
| 5048 | } | ||
| 5049 | else | ||
| 5050 | { | ||
| 5051 | this.Core.ParseExtensionElement(node, child); | ||
| 5052 | } | ||
| 5053 | } | ||
| 5054 | |||
| 5055 | if (!this.Core.EncounteredError) | ||
| 5056 | { | ||
| 5057 | if (ComplexReferenceParentType.Unknown != parentType && YesNoType.Yes != ignoreParent) | ||
| 5058 | { | ||
| 5059 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id, false); | ||
| 5060 | } | ||
| 5061 | } | ||
| 5062 | } | ||
| 5063 | |||
| 5064 | /// <summary> | ||
| 5065 | /// Parses a feature group element. | ||
| 5066 | /// </summary> | ||
| 5067 | /// <param name="node">Element to parse.</param> | ||
| 5068 | /// <param name="parentType"></param> | ||
| 5069 | /// <param name="parentId"></param> | ||
| 5070 | private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 5071 | { | ||
| 5072 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 5073 | Identifier id = null; | ||
| 5074 | |||
| 5075 | foreach (var attrib in node.Attributes()) | ||
| 5076 | { | ||
| 5077 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 5078 | { | ||
| 5079 | switch (attrib.Name.LocalName) | ||
| 5080 | { | ||
| 5081 | case "Id": | ||
| 5082 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 5083 | break; | ||
| 5084 | default: | ||
| 5085 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 5086 | break; | ||
| 5087 | } | ||
| 5088 | } | ||
| 5089 | else | ||
| 5090 | { | ||
| 5091 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 5092 | } | ||
| 5093 | } | ||
| 5094 | |||
| 5095 | if (null == id) | ||
| 5096 | { | ||
| 5097 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 5098 | id = Identifier.Invalid; | ||
| 5099 | } | ||
| 5100 | |||
| 5101 | var lastDisplay = 0; | ||
| 5102 | foreach (var child in node.Elements()) | ||
| 5103 | { | ||
| 5104 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 5105 | { | ||
| 5106 | switch (child.Name.LocalName) | ||
| 5107 | { | ||
| 5108 | case "ComponentGroupRef": | ||
| 5109 | this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); | ||
| 5110 | break; | ||
| 5111 | case "ComponentRef": | ||
| 5112 | this.ParseComponentRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); | ||
| 5113 | break; | ||
| 5114 | case "Component": | ||
| 5115 | this.ParseComponentElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, CompilerConstants.IntegerNotSet, null, null); | ||
| 5116 | break; | ||
| 5117 | case "Feature": | ||
| 5118 | this.ParseFeatureElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, ref lastDisplay); | ||
| 5119 | break; | ||
| 5120 | case "FeatureGroupRef": | ||
| 5121 | this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); | ||
| 5122 | break; | ||
| 5123 | case "FeatureRef": | ||
| 5124 | this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); | ||
| 5125 | break; | ||
| 5126 | case "MergeRef": | ||
| 5127 | this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); | ||
| 5128 | break; | ||
| 5129 | default: | ||
| 5130 | this.Core.UnexpectedElement(node, child); | ||
| 5131 | break; | ||
| 5132 | } | ||
| 5133 | } | ||
| 5134 | else | ||
| 5135 | { | ||
| 5136 | this.Core.ParseExtensionElement(node, child); | ||
| 5137 | } | ||
| 5138 | } | ||
| 5139 | |||
| 5140 | if (!this.Core.EncounteredError) | ||
| 5141 | { | ||
| 5142 | this.Core.AddSymbol(new WixFeatureGroupSymbol(sourceLineNumbers, id)); | ||
| 5143 | |||
| 5144 | //Add this FeatureGroup and its parent in WixGroup. | ||
| 5145 | this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.FeatureGroup, id.Id); | ||
| 5146 | } | ||
| 5147 | } | ||
| 5148 | |||
| 5149 | /// <summary> | ||
| 5150 | /// Parses a feature group reference element. | ||
| 5151 | /// </summary> | ||
| 5152 | /// <param name="node">Element to parse.</param> | ||
| 5153 | /// <param name="parentType">The type of parent.</param> | ||
| 5154 | /// <param name="parentId">Identifier of parent element.</param> | ||
| 5155 | private void ParseFeatureGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 5156 | { | ||
| 5157 | Debug.Assert(ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Product == parentType); | ||
| 5158 | |||
| 5159 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 5160 | string id = null; | ||
| 5161 | var ignoreParent = YesNoType.NotSet; | ||
| 5162 | var primary = YesNoType.NotSet; | ||
| 5163 | |||
| 5164 | foreach (var attrib in node.Attributes()) | ||
| 5165 | { | ||
| 5166 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 5167 | { | ||
| 5168 | switch (attrib.Name.LocalName) | ||
| 5169 | { | ||
| 5170 | case "Id": | ||
| 5171 | id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5172 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixFeatureGroup, id); | ||
| 5173 | break; | ||
| 5174 | case "IgnoreParent": | ||
| 5175 | ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5176 | break; | ||
| 5177 | case "Primary": | ||
| 5178 | primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5179 | break; | ||
| 5180 | default: | ||
| 5181 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 5182 | break; | ||
| 5183 | } | ||
| 5184 | } | ||
| 5185 | else | ||
| 5186 | { | ||
| 5187 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 5188 | } | ||
| 5189 | } | ||
| 5190 | |||
| 5191 | if (null == id) | ||
| 5192 | { | ||
| 5193 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 5194 | } | ||
| 5195 | |||
| 5196 | this.Core.ParseForExtensionElements(node); | ||
| 5197 | |||
| 5198 | if (!this.Core.EncounteredError) | ||
| 5199 | { | ||
| 5200 | if (YesNoType.Yes != ignoreParent) | ||
| 5201 | { | ||
| 5202 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.FeatureGroup, id, (YesNoType.Yes == primary)); | ||
| 5203 | } | ||
| 5204 | } | ||
| 5205 | } | ||
| 5206 | |||
| 5207 | /// <summary> | ||
| 5208 | /// Parses an environment element. | ||
| 5209 | /// </summary> | ||
| 5210 | /// <param name="node">Element to parse.</param> | ||
| 5211 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 5212 | private void ParseEnvironmentElement(XElement node, string componentId) | ||
| 5213 | { | ||
| 5214 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 5215 | Identifier id = null; | ||
| 5216 | string name = null; | ||
| 5217 | EnvironmentActionType? action = null; | ||
| 5218 | EnvironmentPartType? part = null; | ||
| 5219 | var permanent = false; | ||
| 5220 | var separator = ";"; // default to ';' | ||
| 5221 | var system = false; | ||
| 5222 | string value = null; | ||
| 5223 | |||
| 5224 | foreach (var attrib in node.Attributes()) | ||
| 5225 | { | ||
| 5226 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 5227 | { | ||
| 5228 | switch (attrib.Name.LocalName) | ||
| 5229 | { | ||
| 5230 | case "Id": | ||
| 5231 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 5232 | break; | ||
| 5233 | case "Action": | ||
| 5234 | var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5235 | switch (actionValue) | ||
| 5236 | { | ||
| 5237 | case "create": | ||
| 5238 | action = EnvironmentActionType.Create; | ||
| 5239 | break; | ||
| 5240 | case "set": | ||
| 5241 | action = EnvironmentActionType.Set; | ||
| 5242 | break; | ||
| 5243 | case "remove": | ||
| 5244 | action = EnvironmentActionType.Remove; | ||
| 5245 | break; | ||
| 5246 | default: | ||
| 5247 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "create", "set", "remove")); | ||
| 5248 | break; | ||
| 5249 | } | ||
| 5250 | break; | ||
| 5251 | case "Name": | ||
| 5252 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5253 | break; | ||
| 5254 | case "Part": | ||
| 5255 | var partValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5256 | switch (partValue) | ||
| 5257 | { | ||
| 5258 | case "all": | ||
| 5259 | part = EnvironmentPartType.All; | ||
| 5260 | break; | ||
| 5261 | case "first": | ||
| 5262 | part = EnvironmentPartType.First; | ||
| 5263 | break; | ||
| 5264 | case "last": | ||
| 5265 | part = EnvironmentPartType.Last; | ||
| 5266 | break; | ||
| 5267 | case "": | ||
| 5268 | break; | ||
| 5269 | default: | ||
| 5270 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Part", partValue, "all", "first", "last")); | ||
| 5271 | break; | ||
| 5272 | } | ||
| 5273 | break; | ||
| 5274 | case "Permanent": | ||
| 5275 | permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5276 | break; | ||
| 5277 | case "Separator": | ||
| 5278 | separator = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5279 | break; | ||
| 5280 | case "System": | ||
| 5281 | system = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5282 | break; | ||
| 5283 | case "Value": | ||
| 5284 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5285 | break; | ||
| 5286 | default: | ||
| 5287 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 5288 | break; | ||
| 5289 | } | ||
| 5290 | } | ||
| 5291 | else | ||
| 5292 | { | ||
| 5293 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 5294 | } | ||
| 5295 | } | ||
| 5296 | |||
| 5297 | if (null == id) | ||
| 5298 | { | ||
| 5299 | id = this.Core.CreateIdentifier("env", ((int?)action)?.ToString(), name, ((int?)part)?.ToString(), system.ToString()); | ||
| 5300 | } | ||
| 5301 | |||
| 5302 | if (null == name) | ||
| 5303 | { | ||
| 5304 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 5305 | } | ||
| 5306 | |||
| 5307 | if (part.HasValue && action == EnvironmentActionType.Create) | ||
| 5308 | { | ||
| 5309 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); | ||
| 5310 | } | ||
| 5311 | |||
| 5312 | //if (Wix.Environment.PartType.NotSet != partType) | ||
| 5313 | //{ | ||
| 5314 | // if ("+" == action) | ||
| 5315 | // { | ||
| 5316 | // this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); | ||
| 5317 | // } | ||
| 5318 | |||
| 5319 | // switch (partType) | ||
| 5320 | // { | ||
| 5321 | // case Wix.Environment.PartType.all: | ||
| 5322 | // break; | ||
| 5323 | // case Wix.Environment.PartType.first: | ||
| 5324 | // text = String.Concat(text, separator, "[~]"); | ||
| 5325 | // break; | ||
| 5326 | // case Wix.Environment.PartType.last: | ||
| 5327 | // text = String.Concat("[~]", separator, text); | ||
| 5328 | // break; | ||
| 5329 | // } | ||
| 5330 | //} | ||
| 5331 | |||
| 5332 | //if (permanent) | ||
| 5333 | //{ | ||
| 5334 | // uninstall = null; | ||
| 5335 | //} | ||
| 5336 | |||
| 5337 | this.Core.ParseForExtensionElements(node); | ||
| 5338 | |||
| 5339 | if (!this.Core.EncounteredError) | ||
| 5340 | { | ||
| 5341 | this.Core.AddSymbol(new EnvironmentSymbol(sourceLineNumbers, id) | ||
| 5342 | { | ||
| 5343 | Name = name, | ||
| 5344 | Value = value, | ||
| 5345 | Separator = separator, | ||
| 5346 | Action = action, | ||
| 5347 | Part = part, | ||
| 5348 | Permanent = permanent, | ||
| 5349 | System = system, | ||
| 5350 | ComponentRef = componentId | ||
| 5351 | }); | ||
| 5352 | } | ||
| 5353 | } | ||
| 5354 | |||
| 5355 | /// <summary> | ||
| 5356 | /// Parses an error element. | ||
| 5357 | /// </summary> | ||
| 5358 | /// <param name="node">Element to parse.</param> | ||
| 5359 | private void ParseErrorElement(XElement node) | ||
| 5360 | { | ||
| 5361 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 5362 | var id = CompilerConstants.IntegerNotSet; | ||
| 5363 | string message = null; | ||
| 5364 | |||
| 5365 | foreach (var attrib in node.Attributes()) | ||
| 5366 | { | ||
| 5367 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 5368 | { | ||
| 5369 | switch (attrib.Name.LocalName) | ||
| 5370 | { | ||
| 5371 | case "Id": | ||
| 5372 | id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 5373 | break; | ||
| 5374 | case "Message": | ||
| 5375 | message = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 5376 | break; | ||
| 5377 | default: | ||
| 5378 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 5379 | break; | ||
| 5380 | } | ||
| 5381 | } | ||
| 5382 | else | ||
| 5383 | { | ||
| 5384 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 5385 | } | ||
| 5386 | } | ||
| 5387 | |||
| 5388 | if (CompilerConstants.IntegerNotSet == id) | ||
| 5389 | { | ||
| 5390 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 5391 | id = CompilerConstants.IllegalInteger; | ||
| 5392 | } | ||
| 5393 | |||
| 5394 | this.Core.ParseForExtensionElements(node); | ||
| 5395 | |||
| 5396 | if (!this.Core.EncounteredError) | ||
| 5397 | { | ||
| 5398 | this.Core.AddSymbol(new ErrorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) | ||
| 5399 | { | ||
| 5400 | Message = message | ||
| 5401 | }); | ||
| 5402 | } | ||
| 5403 | } | ||
| 5404 | |||
| 5405 | /// <summary> | ||
| 5406 | /// Parses an extension element. | ||
| 5407 | /// </summary> | ||
| 5408 | /// <param name="node">Element to parse.</param> | ||
| 5409 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 5410 | /// <param name="advertise">Flag if this extension is advertised.</param> | ||
| 5411 | /// <param name="progId">ProgId for extension.</param> | ||
| 5412 | private void ParseExtensionElement(XElement node, string componentId, YesNoType advertise, string progId) | ||
| 5413 | { | ||
| 5414 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 5415 | string extension = null; | ||
| 5416 | string mime = null; | ||
| 5417 | |||
| 5418 | foreach (var attrib in node.Attributes()) | ||
| 5419 | { | ||
| 5420 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 5421 | { | ||
| 5422 | switch (attrib.Name.LocalName) | ||
| 5423 | { | ||
| 5424 | case "Id": | ||
| 5425 | extension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5426 | break; | ||
| 5427 | case "Advertise": | ||
| 5428 | var extensionAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5429 | if ((YesNoType.No == advertise && YesNoType.Yes == extensionAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == extensionAdvertise)) | ||
| 5430 | { | ||
| 5431 | this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, extensionAdvertise.ToString(), advertise.ToString())); | ||
| 5432 | } | ||
| 5433 | advertise = extensionAdvertise; | ||
| 5434 | break; | ||
| 5435 | case "ContentType": | ||
| 5436 | mime = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5437 | break; | ||
| 5438 | default: | ||
| 5439 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 5440 | break; | ||
| 5441 | } | ||
| 5442 | } | ||
| 5443 | else | ||
| 5444 | { | ||
| 5445 | var context = new Dictionary<string, string>() { { "ProgId", progId }, { "ComponentId", componentId } }; | ||
| 5446 | this.Core.ParseExtensionAttribute(node, attrib, context); | ||
| 5447 | } | ||
| 5448 | } | ||
| 5449 | |||
| 5450 | if (YesNoType.NotSet == advertise) | ||
| 5451 | { | ||
| 5452 | advertise = YesNoType.No; | ||
| 5453 | } | ||
| 5454 | |||
| 5455 | foreach (var child in node.Elements()) | ||
| 5456 | { | ||
| 5457 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 5458 | { | ||
| 5459 | switch (child.Name.LocalName) | ||
| 5460 | { | ||
| 5461 | case "Verb": | ||
| 5462 | this.ParseVerbElement(child, extension, progId, componentId, advertise); | ||
| 5463 | break; | ||
| 5464 | case "MIME": | ||
| 5465 | var newMime = this.ParseMIMEElement(child, extension, componentId, advertise); | ||
| 5466 | if (null != newMime && null == mime) | ||
| 5467 | { | ||
| 5468 | mime = newMime; | ||
| 5469 | } | ||
| 5470 | break; | ||
| 5471 | default: | ||
| 5472 | this.Core.UnexpectedElement(node, child); | ||
| 5473 | break; | ||
| 5474 | } | ||
| 5475 | } | ||
| 5476 | else | ||
| 5477 | { | ||
| 5478 | this.Core.ParseExtensionElement(node, child); | ||
| 5479 | } | ||
| 5480 | } | ||
| 5481 | |||
| 5482 | |||
| 5483 | if (YesNoType.Yes == advertise) | ||
| 5484 | { | ||
| 5485 | if (!this.Core.EncounteredError) | ||
| 5486 | { | ||
| 5487 | this.Core.AddSymbol(new ExtensionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, extension, componentId)) | ||
| 5488 | { | ||
| 5489 | Extension = extension, | ||
| 5490 | ComponentRef = componentId, | ||
| 5491 | ProgIdRef = progId, | ||
| 5492 | MimeRef = mime, | ||
| 5493 | FeatureRef = Guid.Empty.ToString("B"), | ||
| 5494 | }); | ||
| 5495 | |||
| 5496 | this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Verb); | ||
| 5497 | } | ||
| 5498 | } | ||
| 5499 | else if (YesNoType.No == advertise) | ||
| 5500 | { | ||
| 5501 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), String.Empty, progId, componentId); // Extension | ||
| 5502 | if (null != mime) | ||
| 5503 | { | ||
| 5504 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), "Content Type", mime, componentId); // Extension's MIME ContentType | ||
| 5505 | } | ||
| 5506 | } | ||
| 5507 | } | ||
| 5508 | |||
| 5509 | |||
| 5510 | /// <summary> | ||
| 5511 | /// Parses a file element. | ||
| 5512 | /// </summary> | ||
| 5513 | /// <param name="node">File element to parse.</param> | ||
| 5514 | /// <param name="componentId">Parent's component id.</param> | ||
| 5515 | /// <param name="directoryId">Ancestor's directory id.</param> | ||
| 5516 | /// <param name="diskId">Disk id inherited from parent component.</param> | ||
| 5517 | /// <param name="sourcePath">Default source path of parent directory.</param> | ||
| 5518 | /// <param name="possibleKeyPath">This will be set with the possible keyPath for the parent component.</param> | ||
| 5519 | /// <param name="win64Component">true if the component is 64-bit.</param> | ||
| 5520 | /// <param name="componentGuid"></param> | ||
| 5521 | /// <returns>Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise.</returns> | ||
| 5522 | private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid) | ||
| 5523 | { | ||
| 5524 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 5525 | Identifier id = null; | ||
| 5526 | var assemblyType = AssemblyType.NotAnAssembly; | ||
| 5527 | string assemblyApplication = null; | ||
| 5528 | string assemblyManifest = null; | ||
| 5529 | string bindPath = null; | ||
| 5530 | |||
| 5531 | //int bits = MsiInterop.MsidbFileAttributesVital; | ||
| 5532 | var readOnly = false; | ||
| 5533 | var checksum = false; | ||
| 5534 | bool? compressed = null; | ||
| 5535 | var hidden = false; | ||
| 5536 | var system = false; | ||
| 5537 | var vital = true; // assume all files are vital. | ||
| 5538 | |||
| 5539 | string companionFile = null; | ||
| 5540 | string defaultLanguage = null; | ||
| 5541 | var defaultSize = 0; | ||
| 5542 | string defaultVersion = null; | ||
| 5543 | string fontTitle = null; | ||
| 5544 | var keyPath = YesNoType.NotSet; | ||
| 5545 | string name = null; | ||
| 5546 | var patchGroup = CompilerConstants.IntegerNotSet; | ||
| 5547 | var patchIgnore = false; | ||
| 5548 | var patchIncludeWholeFile = false; | ||
| 5549 | var patchAllowIgnoreOnError = false; | ||
| 5550 | |||
| 5551 | string ignoreLengths = null; | ||
| 5552 | string ignoreOffsets = null; | ||
| 5553 | string protectLengths = null; | ||
| 5554 | string protectOffsets = null; | ||
| 5555 | string symbols = null; | ||
| 5556 | |||
| 5557 | string procArch = null; | ||
| 5558 | int? selfRegCost = null; | ||
| 5559 | string shortName = null; | ||
| 5560 | var source = sourcePath; // assume we'll use the parents as the source for this file | ||
| 5561 | var sourceSet = false; | ||
| 5562 | |||
| 5563 | foreach (var attrib in node.Attributes()) | ||
| 5564 | { | ||
| 5565 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 5566 | { | ||
| 5567 | switch (attrib.Name.LocalName) | ||
| 5568 | { | ||
| 5569 | case "Id": | ||
| 5570 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 5571 | break; | ||
| 5572 | case "Assembly": | ||
| 5573 | var assemblyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5574 | switch (assemblyValue) | ||
| 5575 | { | ||
| 5576 | case ".net": | ||
| 5577 | assemblyType = AssemblyType.DotNetAssembly; | ||
| 5578 | break; | ||
| 5579 | case "no": | ||
| 5580 | assemblyType = AssemblyType.NotAnAssembly; | ||
| 5581 | break; | ||
| 5582 | case "win32": | ||
| 5583 | assemblyType = AssemblyType.Win32Assembly; | ||
| 5584 | break; | ||
| 5585 | default: | ||
| 5586 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "Assembly", assemblyValue, "no", "win32", ".net")); | ||
| 5587 | break; | ||
| 5588 | } | ||
| 5589 | break; | ||
| 5590 | case "AssemblyApplication": | ||
| 5591 | assemblyApplication = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 5592 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyApplication); | ||
| 5593 | break; | ||
| 5594 | case "AssemblyManifest": | ||
| 5595 | assemblyManifest = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 5596 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyManifest); | ||
| 5597 | break; | ||
| 5598 | case "BindPath": | ||
| 5599 | bindPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 5600 | break; | ||
| 5601 | case "Checksum": | ||
| 5602 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 5603 | { | ||
| 5604 | checksum = true; | ||
| 5605 | //bits |= MsiInterop.MsidbFileAttributesChecksum; | ||
| 5606 | } | ||
| 5607 | break; | ||
| 5608 | case "CompanionFile": | ||
| 5609 | companionFile = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 5610 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, companionFile); | ||
| 5611 | break; | ||
| 5612 | case "Compressed": | ||
| 5613 | var compressedValue = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
| 5614 | if (YesNoDefaultType.Yes == compressedValue) | ||
| 5615 | { | ||
| 5616 | compressed = true; | ||
| 5617 | //bits |= MsiInterop.MsidbFileAttributesCompressed; | ||
| 5618 | } | ||
| 5619 | else if (YesNoDefaultType.No == compressedValue) | ||
| 5620 | { | ||
| 5621 | compressed = false; | ||
| 5622 | //bits |= MsiInterop.MsidbFileAttributesNoncompressed; | ||
| 5623 | } | ||
| 5624 | break; | ||
| 5625 | case "DefaultLanguage": | ||
| 5626 | defaultLanguage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5627 | break; | ||
| 5628 | case "DefaultSize": | ||
| 5629 | defaultSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 5630 | break; | ||
| 5631 | case "DefaultVersion": | ||
| 5632 | defaultVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5633 | break; | ||
| 5634 | case "DiskId": | ||
| 5635 | diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 5636 | break; | ||
| 5637 | case "FontTitle": | ||
| 5638 | fontTitle = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5639 | break; | ||
| 5640 | case "Hidden": | ||
| 5641 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 5642 | { | ||
| 5643 | hidden = true; | ||
| 5644 | //bits |= MsiInterop.MsidbFileAttributesHidden; | ||
| 5645 | } | ||
| 5646 | break; | ||
| 5647 | case "KeyPath": | ||
| 5648 | keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5649 | break; | ||
| 5650 | case "Name": | ||
| 5651 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 5652 | break; | ||
| 5653 | case "PatchGroup": | ||
| 5654 | patchGroup = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); | ||
| 5655 | break; | ||
| 5656 | case "PatchIgnore": | ||
| 5657 | patchIgnore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5658 | break; | ||
| 5659 | case "PatchWholeFile": | ||
| 5660 | patchIncludeWholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5661 | break; | ||
| 5662 | case "PatchAllowIgnoreOnError": | ||
| 5663 | patchAllowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5664 | break; | ||
| 5665 | case "ProcessorArchitecture": | ||
| 5666 | var procArchValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5667 | switch (procArchValue) | ||
| 5668 | { | ||
| 5669 | case "msil": | ||
| 5670 | procArch = "MSIL"; | ||
| 5671 | break; | ||
| 5672 | case "x86": | ||
| 5673 | procArch = "x86"; | ||
| 5674 | break; | ||
| 5675 | case "x64": | ||
| 5676 | procArch = "amd64"; | ||
| 5677 | break; | ||
| 5678 | case "arm64": | ||
| 5679 | procArch = "arm64"; | ||
| 5680 | break; | ||
| 5681 | case "": | ||
| 5682 | break; | ||
| 5683 | default: | ||
| 5684 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "ProcessorArchitecture", procArchValue, "msil", "x86", "x64")); | ||
| 5685 | break; | ||
| 5686 | } | ||
| 5687 | break; | ||
| 5688 | case "ReadOnly": | ||
| 5689 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 5690 | { | ||
| 5691 | readOnly = true; | ||
| 5692 | //bits |= MsiInterop.MsidbFileAttributesReadOnly; | ||
| 5693 | } | ||
| 5694 | break; | ||
| 5695 | case "SelfRegCost": | ||
| 5696 | selfRegCost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 5697 | break; | ||
| 5698 | case "ShortName": | ||
| 5699 | shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
| 5700 | break; | ||
| 5701 | case "Source": | ||
| 5702 | source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 5703 | sourceSet = true; | ||
| 5704 | break; | ||
| 5705 | case "System": | ||
| 5706 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 5707 | { | ||
| 5708 | system = true; | ||
| 5709 | //bits |= MsiInterop.MsidbFileAttributesSystem; | ||
| 5710 | } | ||
| 5711 | break; | ||
| 5712 | case "TrueType": | ||
| 5713 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 5714 | { | ||
| 5715 | fontTitle = String.Empty; | ||
| 5716 | } | ||
| 5717 | break; | ||
| 5718 | case "Vital": | ||
| 5719 | var isVital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 5720 | if (YesNoType.Yes == isVital) | ||
| 5721 | { | ||
| 5722 | vital = true; | ||
| 5723 | //bits |= MsiInterop.MsidbFileAttributesVital; | ||
| 5724 | } | ||
| 5725 | else if (YesNoType.No == isVital) | ||
| 5726 | { | ||
| 5727 | vital = false; | ||
| 5728 | //bits &= ~MsiInterop.MsidbFileAttributesVital; | ||
| 5729 | } | ||
| 5730 | break; | ||
| 5731 | default: | ||
| 5732 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 5733 | break; | ||
| 5734 | } | ||
| 5735 | } | ||
| 5736 | else | ||
| 5737 | { | ||
| 5738 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 5739 | } | ||
| 5740 | } | ||
| 5741 | |||
| 5742 | if (null != companionFile) | ||
| 5743 | { | ||
| 5744 | // the companion file cannot be the key path of a component | ||
| 5745 | if (YesNoType.Yes == keyPath) | ||
| 5746 | { | ||
| 5747 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "CompanionFile", "KeyPath", "yes")); | ||
| 5748 | } | ||
| 5749 | } | ||
| 5750 | |||
| 5751 | if (sourceSet && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && null == name) | ||
| 5752 | { | ||
| 5753 | name = Path.GetFileName(source); | ||
| 5754 | if (!this.Core.IsValidLongFilename(name, false)) | ||
| 5755 | { | ||
| 5756 | this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); | ||
| 5757 | } | ||
| 5758 | } | ||
| 5759 | |||
| 5760 | if (name == null) | ||
| 5761 | { | ||
| 5762 | if (shortName == null) | ||
| 5763 | { | ||
| 5764 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 5765 | } | ||
| 5766 | else | ||
| 5767 | { | ||
| 5768 | name = shortName; | ||
| 5769 | shortName = null; | ||
| 5770 | } | ||
| 5771 | } | ||
| 5772 | |||
| 5773 | if (null == id) | ||
| 5774 | { | ||
| 5775 | id = this.Core.CreateIdentifier("fil", directoryId, name); | ||
| 5776 | } | ||
| 5777 | |||
| 5778 | if (null != defaultVersion && null != companionFile) | ||
| 5779 | { | ||
| 5780 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DefaultVersion", "CompanionFile", companionFile)); | ||
| 5781 | } | ||
| 5782 | |||
| 5783 | if (AssemblyType.NotAnAssembly == assemblyType) | ||
| 5784 | { | ||
| 5785 | if (null != assemblyManifest) | ||
| 5786 | { | ||
| 5787 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyManifest")); | ||
| 5788 | } | ||
| 5789 | |||
| 5790 | if (null != assemblyApplication) | ||
| 5791 | { | ||
| 5792 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyApplication")); | ||
| 5793 | } | ||
| 5794 | } | ||
| 5795 | else | ||
| 5796 | { | ||
| 5797 | if (AssemblyType.Win32Assembly == assemblyType && null == assemblyManifest) | ||
| 5798 | { | ||
| 5799 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AssemblyManifest", "Assembly", "win32")); | ||
| 5800 | } | ||
| 5801 | |||
| 5802 | // allow "*" guid components to omit explicit KeyPath as they can have only one file and therefore this file is the keypath | ||
| 5803 | if (YesNoType.Yes != keyPath && "*" != componentGuid) | ||
| 5804 | { | ||
| 5805 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", (AssemblyType.DotNetAssembly == assemblyType ? ".net" : "win32"), "KeyPath", "yes")); | ||
| 5806 | } | ||
| 5807 | } | ||
| 5808 | |||
| 5809 | foreach (var child in node.Elements()) | ||
| 5810 | { | ||
| 5811 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 5812 | { | ||
| 5813 | switch (child.Name.LocalName) | ||
| 5814 | { | ||
| 5815 | case "AppId": | ||
| 5816 | this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null); | ||
| 5817 | break; | ||
| 5818 | case "AssemblyName": | ||
| 5819 | this.ParseAssemblyName(child, componentId); | ||
| 5820 | break; | ||
| 5821 | case "Class": | ||
| 5822 | this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null); | ||
| 5823 | break; | ||
| 5824 | case "CopyFile": | ||
| 5825 | this.ParseCopyFileElement(child, componentId, id.Id); | ||
| 5826 | break; | ||
| 5827 | case "IgnoreRange": | ||
| 5828 | this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); | ||
| 5829 | break; | ||
| 5830 | case "ODBCDriver": | ||
| 5831 | this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCDriver); | ||
| 5832 | break; | ||
| 5833 | case "ODBCTranslator": | ||
| 5834 | this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCTranslator); | ||
| 5835 | break; | ||
| 5836 | case "Permission": | ||
| 5837 | this.ParsePermissionElement(child, id.Id, "File"); | ||
| 5838 | break; | ||
| 5839 | case "PermissionEx": | ||
| 5840 | this.ParsePermissionExElement(child, id.Id, "File"); | ||
| 5841 | break; | ||
| 5842 | case "ProtectRange": | ||
| 5843 | this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); | ||
| 5844 | break; | ||
| 5845 | case "Shortcut": | ||
| 5846 | this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath); | ||
| 5847 | break; | ||
| 5848 | case "SymbolPath": | ||
| 5849 | if (null != symbols) | ||
| 5850 | { | ||
| 5851 | symbols += ";" + this.ParseSymbolPathElement(child); | ||
| 5852 | } | ||
| 5853 | else | ||
| 5854 | { | ||
| 5855 | symbols = this.ParseSymbolPathElement(child); | ||
| 5856 | } | ||
| 5857 | break; | ||
| 5858 | case "TypeLib": | ||
| 5859 | this.ParseTypeLibElement(child, componentId, id.Id, win64Component); | ||
| 5860 | break; | ||
| 5861 | default: | ||
| 5862 | this.Core.UnexpectedElement(node, child); | ||
| 5863 | break; | ||
| 5864 | } | ||
| 5865 | } | ||
| 5866 | else | ||
| 5867 | { | ||
| 5868 | var context = new Dictionary<string, string>() { { "FileId", id?.Id }, { "ComponentId", componentId }, { "DirectoryId", directoryId }, { "Win64", win64Component.ToString() } }; | ||
| 5869 | this.Core.ParseExtensionElement(node, child, context); | ||
| 5870 | } | ||
| 5871 | } | ||
| 5872 | |||
| 5873 | if (!this.Core.EncounteredError) | ||
| 5874 | { | ||
| 5875 | var patchAttributes = PatchAttributeType.None; | ||
| 5876 | if (patchIgnore) | ||
| 5877 | { | ||
| 5878 | patchAttributes |= PatchAttributeType.Ignore; | ||
| 5879 | } | ||
| 5880 | if (patchIncludeWholeFile) | ||
| 5881 | { | ||
| 5882 | patchAttributes |= PatchAttributeType.IncludeWholeFile; | ||
| 5883 | } | ||
| 5884 | if (patchAllowIgnoreOnError) | ||
| 5885 | { | ||
| 5886 | patchAttributes |= PatchAttributeType.AllowIgnoreOnError; | ||
| 5887 | } | ||
| 5888 | |||
| 5889 | if (String.IsNullOrEmpty(source)) | ||
| 5890 | { | ||
| 5891 | source = name; | ||
| 5892 | } | ||
| 5893 | else if (source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) // if source relies on parent directories, append the file name | ||
| 5894 | { | ||
| 5895 | source = Path.Combine(source, name); | ||
| 5896 | } | ||
| 5897 | |||
| 5898 | var attributes = FileSymbolAttributes.None; | ||
| 5899 | attributes |= readOnly ? FileSymbolAttributes.ReadOnly : 0; | ||
| 5900 | attributes |= hidden ? FileSymbolAttributes.Hidden : 0; | ||
| 5901 | attributes |= system ? FileSymbolAttributes.System : 0; | ||
| 5902 | attributes |= vital ? FileSymbolAttributes.Vital : 0; | ||
| 5903 | attributes |= checksum ? FileSymbolAttributes.Checksum : 0; | ||
| 5904 | attributes |= compressed.HasValue && compressed == true ? FileSymbolAttributes.Compressed : 0; | ||
| 5905 | attributes |= compressed.HasValue && compressed == false ? FileSymbolAttributes.Uncompressed : 0; | ||
| 5906 | |||
| 5907 | this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) | ||
| 5908 | { | ||
| 5909 | ComponentRef = componentId, | ||
| 5910 | Name = name, | ||
| 5911 | ShortName = shortName, | ||
| 5912 | FileSize = defaultSize, | ||
| 5913 | Version = companionFile ?? defaultVersion, | ||
| 5914 | Language = defaultLanguage, | ||
| 5915 | Attributes = attributes, | ||
| 5916 | |||
| 5917 | DirectoryRef = directoryId, | ||
| 5918 | DiskId = (CompilerConstants.IntegerNotSet == diskId) ? null : (int?)diskId, | ||
| 5919 | Source = new IntermediateFieldPathValue { Path = source }, | ||
| 5920 | |||
| 5921 | FontTitle = fontTitle, | ||
| 5922 | SelfRegCost = selfRegCost, | ||
| 5923 | BindPath = bindPath, | ||
| 5924 | |||
| 5925 | PatchGroup = (CompilerConstants.IntegerNotSet == patchGroup) ? null : (int?)patchGroup, | ||
| 5926 | PatchAttributes = patchAttributes, | ||
| 5927 | |||
| 5928 | // Delta patching information | ||
| 5929 | RetainLengths = protectLengths, | ||
| 5930 | IgnoreOffsets = ignoreOffsets, | ||
| 5931 | IgnoreLengths = ignoreLengths, | ||
| 5932 | RetainOffsets = protectOffsets, | ||
| 5933 | SymbolPaths = symbols, | ||
| 5934 | }); | ||
| 5935 | |||
| 5936 | if (AssemblyType.NotAnAssembly != assemblyType) | ||
| 5937 | { | ||
| 5938 | this.Core.AddSymbol(new AssemblySymbol(sourceLineNumbers, id) | ||
| 5939 | { | ||
| 5940 | ComponentRef = componentId, | ||
| 5941 | FeatureRef = Guid.Empty.ToString("B"), | ||
| 5942 | ManifestFileRef = assemblyManifest, | ||
| 5943 | ApplicationFileRef = assemblyApplication, | ||
| 5944 | Type = assemblyType, | ||
| 5945 | ProcessorArchitecture = procArch, | ||
| 5946 | }); | ||
| 5947 | } | ||
| 5948 | } | ||
| 5949 | |||
| 5950 | if (CompilerConstants.IntegerNotSet != diskId) | ||
| 5951 | { | ||
| 5952 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); | ||
| 5953 | } | ||
| 5954 | |||
| 5955 | // If this component does not have a companion file this file is a possible keypath. | ||
| 5956 | possibleKeyPath = null; | ||
| 5957 | if (null == companionFile) | ||
| 5958 | { | ||
| 5959 | possibleKeyPath = id.Id; | ||
| 5960 | } | ||
| 5961 | |||
| 5962 | return keyPath; | ||
| 5963 | } | ||
| 5964 | |||
| 5965 | /// <summary> | ||
| 5966 | /// Parses a file search element. | ||
| 5967 | /// </summary> | ||
| 5968 | /// <param name="node">Element to parse.</param> | ||
| 5969 | /// <param name="parentSignature">Signature of parent search element.</param> | ||
| 5970 | /// <param name="parentDirectorySearch">Whether this search element is used to search for the parent directory.</param> | ||
| 5971 | /// <param name="parentDepth">The depth specified by the parent search element.</param> | ||
| 5972 | /// <returns>Signature of search element.</returns> | ||
| 5973 | private string ParseFileSearchElement(XElement node, string parentSignature, bool parentDirectorySearch, int parentDepth) | ||
| 5974 | { | ||
| 5975 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 5976 | Identifier id = null; | ||
| 5977 | string languages = null; | ||
| 5978 | var minDate = CompilerConstants.IntegerNotSet; | ||
| 5979 | var maxDate = CompilerConstants.IntegerNotSet; | ||
| 5980 | var maxSize = CompilerConstants.IntegerNotSet; | ||
| 5981 | var minSize = CompilerConstants.IntegerNotSet; | ||
| 5982 | string maxVersion = null; | ||
| 5983 | string minVersion = null; | ||
| 5984 | string name = null; | ||
| 5985 | string shortName = null; | ||
| 5986 | |||
| 5987 | foreach (var attrib in node.Attributes()) | ||
| 5988 | { | ||
| 5989 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 5990 | { | ||
| 5991 | switch (attrib.Name.LocalName) | ||
| 5992 | { | ||
| 5993 | case "Id": | ||
| 5994 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 5995 | break; | ||
| 5996 | case "Name": | ||
| 5997 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 5998 | break; | ||
| 5999 | case "MinVersion": | ||
| 6000 | minVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6001 | break; | ||
| 6002 | case "MaxVersion": | ||
| 6003 | maxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6004 | break; | ||
| 6005 | case "MinSize": | ||
| 6006 | minSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 6007 | break; | ||
| 6008 | case "MaxSize": | ||
| 6009 | maxSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 6010 | break; | ||
| 6011 | case "MinDate": | ||
| 6012 | minDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); | ||
| 6013 | break; | ||
| 6014 | case "MaxDate": | ||
| 6015 | maxDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); | ||
| 6016 | break; | ||
| 6017 | case "Languages": | ||
| 6018 | languages = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6019 | break; | ||
| 6020 | case "ShortName": | ||
| 6021 | shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
| 6022 | break; | ||
| 6023 | default: | ||
| 6024 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 6025 | break; | ||
| 6026 | } | ||
| 6027 | } | ||
| 6028 | else | ||
| 6029 | { | ||
| 6030 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 6031 | } | ||
| 6032 | } | ||
| 6033 | |||
| 6034 | // Using both ShortName and Name will not always work due to a Windows Installer bug. | ||
| 6035 | if (null != shortName && null != name) | ||
| 6036 | { | ||
| 6037 | this.Core.Write(WarningMessages.FileSearchFileNameIssue(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); | ||
| 6038 | } | ||
| 6039 | else if (null == shortName && null == name) // at least one name must be specified. | ||
| 6040 | { | ||
| 6041 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 6042 | } | ||
| 6043 | |||
| 6044 | if (this.Core.IsValidShortFilename(name, false)) | ||
| 6045 | { | ||
| 6046 | if (null == shortName) | ||
| 6047 | { | ||
| 6048 | shortName = name; | ||
| 6049 | name = null; | ||
| 6050 | } | ||
| 6051 | else | ||
| 6052 | { | ||
| 6053 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); | ||
| 6054 | } | ||
| 6055 | } | ||
| 6056 | |||
| 6057 | if (null == id) | ||
| 6058 | { | ||
| 6059 | if (String.IsNullOrEmpty(parentSignature)) | ||
| 6060 | { | ||
| 6061 | id = this.Core.CreateIdentifier("fs", name ?? shortName); | ||
| 6062 | } | ||
| 6063 | else // reuse parent signature in the Signature table | ||
| 6064 | { | ||
| 6065 | id = new Identifier(AccessModifier.Section, parentSignature); | ||
| 6066 | } | ||
| 6067 | } | ||
| 6068 | |||
| 6069 | var isSameId = String.Equals(id.Id, parentSignature, StringComparison.Ordinal); | ||
| 6070 | if (parentDirectorySearch) | ||
| 6071 | { | ||
| 6072 | // If searching for the parent directory, the Id attribute | ||
| 6073 | // value must be specified and unique. | ||
| 6074 | if (isSameId) | ||
| 6075 | { | ||
| 6076 | this.Core.Write(ErrorMessages.UniqueFileSearchIdRequired(sourceLineNumbers, parentSignature, node.Name.LocalName)); | ||
| 6077 | } | ||
| 6078 | } | ||
| 6079 | else if (parentDepth > 1) | ||
| 6080 | { | ||
| 6081 | // Otherwise, if the depth > 1 the Id must be absent or the same | ||
| 6082 | // as the parent DirectorySearch if AssignToProperty is not set. | ||
| 6083 | if (!isSameId) | ||
| 6084 | { | ||
| 6085 | this.Core.Write(ErrorMessages.IllegalSearchIdForParentDepth(sourceLineNumbers, id.Id, parentSignature)); | ||
| 6086 | } | ||
| 6087 | } | ||
| 6088 | |||
| 6089 | this.Core.ParseForExtensionElements(node); | ||
| 6090 | |||
| 6091 | if (!this.Core.EncounteredError) | ||
| 6092 | { | ||
| 6093 | var symbol = this.Core.AddSymbol(new SignatureSymbol(sourceLineNumbers, id) | ||
| 6094 | { | ||
| 6095 | FileName = name ?? shortName, | ||
| 6096 | MinVersion = minVersion, | ||
| 6097 | MaxVersion = maxVersion, | ||
| 6098 | Languages = languages | ||
| 6099 | }); | ||
| 6100 | |||
| 6101 | if (CompilerConstants.IntegerNotSet != minSize) | ||
| 6102 | { | ||
| 6103 | symbol.MinSize = minSize; | ||
| 6104 | } | ||
| 6105 | |||
| 6106 | if (CompilerConstants.IntegerNotSet != maxSize) | ||
| 6107 | { | ||
| 6108 | symbol.MaxSize = maxSize; | ||
| 6109 | } | ||
| 6110 | |||
| 6111 | if (CompilerConstants.IntegerNotSet != minDate) | ||
| 6112 | { | ||
| 6113 | symbol.MinDate = minDate; | ||
| 6114 | } | ||
| 6115 | |||
| 6116 | if (CompilerConstants.IntegerNotSet != maxDate) | ||
| 6117 | { | ||
| 6118 | symbol.MaxDate = maxDate; | ||
| 6119 | } | ||
| 6120 | |||
| 6121 | // Create a DrLocator row to associate the file with a directory | ||
| 6122 | // when a different identifier is specified for the FileSearch. | ||
| 6123 | if (!isSameId) | ||
| 6124 | { | ||
| 6125 | if (parentDirectorySearch) | ||
| 6126 | { | ||
| 6127 | // Creates the DrLocator row for the directory search while | ||
| 6128 | // the parent DirectorySearch creates the file locator row. | ||
| 6129 | this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, parentSignature, id.Id, String.Empty)) | ||
| 6130 | { | ||
| 6131 | SignatureRef = parentSignature, | ||
| 6132 | Parent = id.Id | ||
| 6133 | }); | ||
| 6134 | } | ||
| 6135 | else | ||
| 6136 | { | ||
| 6137 | this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id.Id, parentSignature, String.Empty)) | ||
| 6138 | { | ||
| 6139 | SignatureRef = id.Id, | ||
| 6140 | Parent = parentSignature | ||
| 6141 | }); | ||
| 6142 | } | ||
| 6143 | } | ||
| 6144 | } | ||
| 6145 | |||
| 6146 | return id.Id; // the id of the FileSearch element is its signature | ||
| 6147 | } | ||
| 6148 | |||
| 6149 | |||
| 6150 | /// <summary> | ||
| 6151 | /// Parses a fragment element. | ||
| 6152 | /// </summary> | ||
| 6153 | /// <param name="node">Element to parse.</param> | ||
| 6154 | private void ParseFragmentElement(XElement node) | ||
| 6155 | { | ||
| 6156 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 6157 | Identifier id = null; | ||
| 6158 | |||
| 6159 | this.activeName = null; | ||
| 6160 | this.activeLanguage = null; | ||
| 6161 | |||
| 6162 | foreach (var attrib in node.Attributes()) | ||
| 6163 | { | ||
| 6164 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 6165 | { | ||
| 6166 | switch (attrib.Name.LocalName) | ||
| 6167 | { | ||
| 6168 | case "Id": | ||
| 6169 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 6170 | break; | ||
| 6171 | default: | ||
| 6172 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 6173 | break; | ||
| 6174 | } | ||
| 6175 | } | ||
| 6176 | else | ||
| 6177 | { | ||
| 6178 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 6179 | } | ||
| 6180 | } | ||
| 6181 | |||
| 6182 | // NOTE: Id is not required for Fragments, this is a departure from the normal run of the mill processing. | ||
| 6183 | |||
| 6184 | this.Core.CreateActiveSection(id?.Id, SectionType.Fragment, this.Context.CompilationId); | ||
| 6185 | |||
| 6186 | var featureDisplay = 0; | ||
| 6187 | foreach (var child in node.Elements()) | ||
| 6188 | { | ||
| 6189 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 6190 | { | ||
| 6191 | switch (child.Name.LocalName) | ||
| 6192 | { | ||
| 6193 | case "_locDefinition": | ||
| 6194 | break; | ||
| 6195 | case "AdminExecuteSequence": | ||
| 6196 | this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); | ||
| 6197 | break; | ||
| 6198 | case "AdminUISequence": | ||
| 6199 | this.ParseSequenceElement(child, SequenceTable.AdminUISequence); | ||
| 6200 | break; | ||
| 6201 | case "AdvertiseExecuteSequence": | ||
| 6202 | this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); | ||
| 6203 | break; | ||
| 6204 | case "InstallExecuteSequence": | ||
| 6205 | this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); | ||
| 6206 | break; | ||
| 6207 | case "InstallUISequence": | ||
| 6208 | this.ParseSequenceElement(child, SequenceTable.InstallUISequence); | ||
| 6209 | break; | ||
| 6210 | case "AppId": | ||
| 6211 | this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); | ||
| 6212 | break; | ||
| 6213 | case "Binary": | ||
| 6214 | this.ParseBinaryElement(child); | ||
| 6215 | break; | ||
| 6216 | case "BootstrapperApplication": | ||
| 6217 | this.ParseBootstrapperApplicationElement(child); | ||
| 6218 | break; | ||
| 6219 | case "BootstrapperApplicationRef": | ||
| 6220 | this.ParseBootstrapperApplicationRefElement(child); | ||
| 6221 | break; | ||
| 6222 | case "BundleCustomData": | ||
| 6223 | this.ParseBundleCustomDataElement(child); | ||
| 6224 | break; | ||
| 6225 | case "BundleCustomDataRef": | ||
| 6226 | this.ParseBundleCustomDataRefElement(child); | ||
| 6227 | break; | ||
| 6228 | case "BundleExtension": | ||
| 6229 | this.ParseBundleExtensionElement(child); | ||
| 6230 | break; | ||
| 6231 | case "BundleExtensionRef": | ||
| 6232 | this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); | ||
| 6233 | break; | ||
| 6234 | case "ComplianceCheck": | ||
| 6235 | this.ParseComplianceCheckElement(child); | ||
| 6236 | break; | ||
| 6237 | case "Component": | ||
| 6238 | this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); | ||
| 6239 | break; | ||
| 6240 | case "ComponentGroup": | ||
| 6241 | this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); | ||
| 6242 | break; | ||
| 6243 | case "Container": | ||
| 6244 | this.ParseContainerElement(child); | ||
| 6245 | break; | ||
| 6246 | case "CustomAction": | ||
| 6247 | this.ParseCustomActionElement(child); | ||
| 6248 | break; | ||
| 6249 | case "CustomActionRef": | ||
| 6250 | this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); | ||
| 6251 | break; | ||
| 6252 | case "CustomTable": | ||
| 6253 | this.ParseCustomTableElement(child); | ||
| 6254 | break; | ||
| 6255 | case "CustomTableRef": | ||
| 6256 | this.ParseCustomTableRefElement(child); | ||
| 6257 | break; | ||
| 6258 | case "Directory": | ||
| 6259 | this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); | ||
| 6260 | break; | ||
| 6261 | case "DirectoryRef": | ||
| 6262 | this.ParseDirectoryRefElement(child); | ||
| 6263 | break; | ||
| 6264 | case "EmbeddedChainer": | ||
| 6265 | this.ParseEmbeddedChainerElement(child); | ||
| 6266 | break; | ||
| 6267 | case "EmbeddedChainerRef": | ||
| 6268 | this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); | ||
| 6269 | break; | ||
| 6270 | case "EnsureTable": | ||
| 6271 | this.ParseEnsureTableElement(child); | ||
| 6272 | break; | ||
| 6273 | case "Feature": | ||
| 6274 | this.ParseFeatureElement(child, ComplexReferenceParentType.Unknown, null, ref featureDisplay); | ||
| 6275 | break; | ||
| 6276 | case "FeatureGroup": | ||
| 6277 | this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); | ||
| 6278 | break; | ||
| 6279 | case "FeatureRef": | ||
| 6280 | this.ParseFeatureRefElement(child, ComplexReferenceParentType.Unknown, null); | ||
| 6281 | break; | ||
| 6282 | case "Icon": | ||
| 6283 | this.ParseIconElement(child); | ||
| 6284 | break; | ||
| 6285 | case "Media": | ||
| 6286 | this.ParseMediaElement(child, null); | ||
| 6287 | break; | ||
| 6288 | case "MediaTemplate": | ||
| 6289 | this.ParseMediaTemplateElement(child, null); | ||
| 6290 | break; | ||
| 6291 | case "Launch": | ||
| 6292 | this.ParseLaunchElement(child); | ||
| 6293 | break; | ||
| 6294 | case "PackageGroup": | ||
| 6295 | this.ParsePackageGroupElement(child); | ||
| 6296 | break; | ||
| 6297 | case "PackageCertificates": | ||
| 6298 | case "PatchCertificates": | ||
| 6299 | this.ParseCertificatesElement(child); | ||
| 6300 | break; | ||
| 6301 | case "PatchFamily": | ||
| 6302 | this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id.Id); | ||
| 6303 | break; | ||
| 6304 | case "PatchFamilyGroup": | ||
| 6305 | this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id.Id); | ||
| 6306 | break; | ||
| 6307 | case "PatchFamilyGroupRef": | ||
| 6308 | this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id.Id); | ||
| 6309 | break; | ||
| 6310 | case "PayloadGroup": | ||
| 6311 | this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null); | ||
| 6312 | break; | ||
| 6313 | case "Property": | ||
| 6314 | this.ParsePropertyElement(child); | ||
| 6315 | break; | ||
| 6316 | case "PropertyRef": | ||
| 6317 | this.ParseSimpleRefElement(child, SymbolDefinitions.Property); | ||
| 6318 | break; | ||
| 6319 | case "RelatedBundle": | ||
| 6320 | this.ParseRelatedBundleElement(child); | ||
| 6321 | break; | ||
| 6322 | case "Requires": | ||
| 6323 | this.ParseRequiresElement(child, null); | ||
| 6324 | break; | ||
| 6325 | case "SetDirectory": | ||
| 6326 | this.ParseSetDirectoryElement(child); | ||
| 6327 | break; | ||
| 6328 | case "SetProperty": | ||
| 6329 | this.ParseSetPropertyElement(child); | ||
| 6330 | break; | ||
| 6331 | case "SetVariable": | ||
| 6332 | this.ParseSetVariableElement(child); | ||
| 6333 | break; | ||
| 6334 | case "SetVariableRef": | ||
| 6335 | this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); | ||
| 6336 | break; | ||
| 6337 | case "SFPCatalog": | ||
| 6338 | string parentName = null; | ||
| 6339 | this.ParseSFPCatalogElement(child, ref parentName); | ||
| 6340 | break; | ||
| 6341 | case "StandardDirectory": | ||
| 6342 | this.ParseStandardDirectoryElement(child); | ||
| 6343 | break; | ||
| 6344 | case "UI": | ||
| 6345 | this.ParseUIElement(child); | ||
| 6346 | break; | ||
| 6347 | case "UIRef": | ||
| 6348 | this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); | ||
| 6349 | break; | ||
| 6350 | case "Upgrade": | ||
| 6351 | this.ParseUpgradeElement(child); | ||
| 6352 | break; | ||
| 6353 | case "Variable": | ||
| 6354 | this.ParseVariableElement(child); | ||
| 6355 | break; | ||
| 6356 | case "WixVariable": | ||
| 6357 | this.ParseWixVariableElement(child); | ||
| 6358 | break; | ||
| 6359 | default: | ||
| 6360 | this.Core.UnexpectedElement(node, child); | ||
| 6361 | break; | ||
| 6362 | } | ||
| 6363 | } | ||
| 6364 | else | ||
| 6365 | { | ||
| 6366 | this.Core.ParseExtensionElement(node, child); | ||
| 6367 | } | ||
| 6368 | } | ||
| 6369 | |||
| 6370 | if (!this.Core.EncounteredError && null != id) | ||
| 6371 | { | ||
| 6372 | this.Core.AddSymbol(new WixFragmentSymbol(sourceLineNumbers, id)); | ||
| 6373 | } | ||
| 6374 | } | ||
| 6375 | |||
| 6376 | /// <summary> | ||
| 6377 | /// Parses a launch condition element. | ||
| 6378 | /// </summary> | ||
| 6379 | /// <param name="node">Element to parse.</param> | ||
| 6380 | private void ParseLaunchElement(XElement node) | ||
| 6381 | { | ||
| 6382 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 6383 | string condition = null; | ||
| 6384 | string message = null; | ||
| 6385 | |||
| 6386 | foreach (var attrib in node.Attributes()) | ||
| 6387 | { | ||
| 6388 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 6389 | { | ||
| 6390 | switch (attrib.Name.LocalName) | ||
| 6391 | { | ||
| 6392 | case "Condition": | ||
| 6393 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6394 | break; | ||
| 6395 | case "Message": | ||
| 6396 | message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6397 | break; | ||
| 6398 | default: | ||
| 6399 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 6400 | break; | ||
| 6401 | } | ||
| 6402 | } | ||
| 6403 | else | ||
| 6404 | { | ||
| 6405 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 6406 | } | ||
| 6407 | } | ||
| 6408 | |||
| 6409 | if (String.IsNullOrEmpty(condition)) | ||
| 6410 | { | ||
| 6411 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); | ||
| 6412 | } | ||
| 6413 | |||
| 6414 | if (String.IsNullOrEmpty(message)) | ||
| 6415 | { | ||
| 6416 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Message")); | ||
| 6417 | } | ||
| 6418 | |||
| 6419 | |||
| 6420 | this.Core.ParseForExtensionElements(node); | ||
| 6421 | |||
| 6422 | if (!this.Core.EncounteredError) | ||
| 6423 | { | ||
| 6424 | this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) | ||
| 6425 | { | ||
| 6426 | Condition = condition, | ||
| 6427 | Description = message | ||
| 6428 | }); | ||
| 6429 | } | ||
| 6430 | } | ||
| 6431 | |||
| 6432 | /// <summary> | ||
| 6433 | /// Parses a IniFile element. | ||
| 6434 | /// </summary> | ||
| 6435 | /// <param name="node">Element to parse.</param> | ||
| 6436 | /// <param name="componentId">Identifier of the parent component.</param> | ||
| 6437 | private void ParseIniFileElement(XElement node, string componentId) | ||
| 6438 | { | ||
| 6439 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 6440 | Identifier id = null; | ||
| 6441 | IniFileActionType? action = null; | ||
| 6442 | string directory = null; | ||
| 6443 | string key = null; | ||
| 6444 | string name = null; | ||
| 6445 | string section = null; | ||
| 6446 | string shortName = null; | ||
| 6447 | string value = null; | ||
| 6448 | |||
| 6449 | foreach (var attrib in node.Attributes()) | ||
| 6450 | { | ||
| 6451 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 6452 | { | ||
| 6453 | switch (attrib.Name.LocalName) | ||
| 6454 | { | ||
| 6455 | case "Id": | ||
| 6456 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 6457 | break; | ||
| 6458 | case "Action": | ||
| 6459 | var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6460 | switch (actionValue) | ||
| 6461 | { | ||
| 6462 | case "addLine": | ||
| 6463 | action = IniFileActionType.AddLine; | ||
| 6464 | break; | ||
| 6465 | case "addTag": | ||
| 6466 | action = IniFileActionType.AddTag; | ||
| 6467 | break; | ||
| 6468 | case "removeLine": | ||
| 6469 | action = IniFileActionType.RemoveLine; | ||
| 6470 | break; | ||
| 6471 | case "removeTag": | ||
| 6472 | action = IniFileActionType.RemoveTag; | ||
| 6473 | break; | ||
| 6474 | case "": // error case handled by GetAttributeValue() | ||
| 6475 | break; | ||
| 6476 | default: | ||
| 6477 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", actionValue, "addLine", "addTag", "createLine", "removeLine", "removeTag")); | ||
| 6478 | break; | ||
| 6479 | } | ||
| 6480 | break; | ||
| 6481 | case "Directory": | ||
| 6482 | directory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 6483 | break; | ||
| 6484 | case "Key": | ||
| 6485 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6486 | break; | ||
| 6487 | case "Name": | ||
| 6488 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 6489 | break; | ||
| 6490 | case "Section": | ||
| 6491 | section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6492 | break; | ||
| 6493 | case "ShortName": | ||
| 6494 | shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
| 6495 | break; | ||
| 6496 | case "Value": | ||
| 6497 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6498 | break; | ||
| 6499 | default: | ||
| 6500 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 6501 | break; | ||
| 6502 | } | ||
| 6503 | } | ||
| 6504 | else | ||
| 6505 | { | ||
| 6506 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 6507 | } | ||
| 6508 | } | ||
| 6509 | |||
| 6510 | if (!action.HasValue) | ||
| 6511 | { | ||
| 6512 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); | ||
| 6513 | } | ||
| 6514 | else if (IniFileActionType.AddLine == action || IniFileActionType.AddTag == action || IniFileActionType.CreateLine == action) | ||
| 6515 | { | ||
| 6516 | if (null == value) | ||
| 6517 | { | ||
| 6518 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 6519 | } | ||
| 6520 | } | ||
| 6521 | |||
| 6522 | if (null == key) | ||
| 6523 | { | ||
| 6524 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 6525 | } | ||
| 6526 | |||
| 6527 | if (null == name) | ||
| 6528 | { | ||
| 6529 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 6530 | } | ||
| 6531 | |||
| 6532 | if (null == section) | ||
| 6533 | { | ||
| 6534 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); | ||
| 6535 | } | ||
| 6536 | |||
| 6537 | if (null == id) | ||
| 6538 | { | ||
| 6539 | id = this.Core.CreateIdentifier("ini", directory, name ?? shortName, section, key, name); | ||
| 6540 | } | ||
| 6541 | |||
| 6542 | this.Core.ParseForExtensionElements(node); | ||
| 6543 | |||
| 6544 | if (!this.Core.EncounteredError) | ||
| 6545 | { | ||
| 6546 | this.Core.AddSymbol(new IniFileSymbol(sourceLineNumbers, id) | ||
| 6547 | { | ||
| 6548 | FileName = name, | ||
| 6549 | ShortFileName = shortName, | ||
| 6550 | DirProperty = directory, | ||
| 6551 | Section = section, | ||
| 6552 | Key = key, | ||
| 6553 | Value = value, | ||
| 6554 | Action = action.Value, | ||
| 6555 | ComponentRef = componentId | ||
| 6556 | }); | ||
| 6557 | } | ||
| 6558 | } | ||
| 6559 | |||
| 6560 | /// <summary> | ||
| 6561 | /// Parses an IniFile search element. | ||
| 6562 | /// </summary> | ||
| 6563 | /// <param name="node">Element to parse.</param> | ||
| 6564 | /// <returns>Signature for search element.</returns> | ||
| 6565 | private string ParseIniFileSearchElement(XElement node) | ||
| 6566 | { | ||
| 6567 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 6568 | Identifier id = null; | ||
| 6569 | var field = CompilerConstants.IntegerNotSet; | ||
| 6570 | string key = null; | ||
| 6571 | string name = null; | ||
| 6572 | string section = null; | ||
| 6573 | string shortName = null; | ||
| 6574 | string signature = null; | ||
| 6575 | var type = 1; // default is file | ||
| 6576 | |||
| 6577 | foreach (var attrib in node.Attributes()) | ||
| 6578 | { | ||
| 6579 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 6580 | { | ||
| 6581 | switch (attrib.Name.LocalName) | ||
| 6582 | { | ||
| 6583 | case "Id": | ||
| 6584 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 6585 | break; | ||
| 6586 | case "Field": | ||
| 6587 | field = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 6588 | break; | ||
| 6589 | case "Key": | ||
| 6590 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6591 | break; | ||
| 6592 | case "Name": | ||
| 6593 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 6594 | break; | ||
| 6595 | case "Section": | ||
| 6596 | section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6597 | break; | ||
| 6598 | case "ShortName": | ||
| 6599 | shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
| 6600 | break; | ||
| 6601 | case "Type": | ||
| 6602 | var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6603 | switch (typeValue) | ||
| 6604 | { | ||
| 6605 | case "directory": | ||
| 6606 | type = 0; | ||
| 6607 | break; | ||
| 6608 | case "file": | ||
| 6609 | type = 1; | ||
| 6610 | break; | ||
| 6611 | case "raw": | ||
| 6612 | type = 2; | ||
| 6613 | break; | ||
| 6614 | case "": | ||
| 6615 | break; | ||
| 6616 | default: | ||
| 6617 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "registry")); | ||
| 6618 | break; | ||
| 6619 | } | ||
| 6620 | break; | ||
| 6621 | default: | ||
| 6622 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 6623 | break; | ||
| 6624 | } | ||
| 6625 | } | ||
| 6626 | else | ||
| 6627 | { | ||
| 6628 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 6629 | } | ||
| 6630 | } | ||
| 6631 | |||
| 6632 | if (null == key) | ||
| 6633 | { | ||
| 6634 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 6635 | } | ||
| 6636 | |||
| 6637 | if (null == name) | ||
| 6638 | { | ||
| 6639 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 6640 | } | ||
| 6641 | |||
| 6642 | if (null == section) | ||
| 6643 | { | ||
| 6644 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); | ||
| 6645 | } | ||
| 6646 | |||
| 6647 | if (null == id) | ||
| 6648 | { | ||
| 6649 | id = this.Core.CreateIdentifier("ini", name, section, key, field.ToString(), type.ToString()); | ||
| 6650 | } | ||
| 6651 | |||
| 6652 | signature = id.Id; | ||
| 6653 | |||
| 6654 | var oneChild = false; | ||
| 6655 | foreach (var child in node.Elements()) | ||
| 6656 | { | ||
| 6657 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 6658 | { | ||
| 6659 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 6660 | switch (child.Name.LocalName) | ||
| 6661 | { | ||
| 6662 | case "DirectorySearch": | ||
| 6663 | if (oneChild) | ||
| 6664 | { | ||
| 6665 | this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
| 6666 | } | ||
| 6667 | oneChild = true; | ||
| 6668 | |||
| 6669 | // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column | ||
| 6670 | signature = this.ParseDirectorySearchElement(child, id.Id); | ||
| 6671 | break; | ||
| 6672 | case "DirectorySearchRef": | ||
| 6673 | if (oneChild) | ||
| 6674 | { | ||
| 6675 | this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); | ||
| 6676 | } | ||
| 6677 | oneChild = true; | ||
| 6678 | signature = this.ParseDirectorySearchRefElement(child, id.Id); | ||
| 6679 | break; | ||
| 6680 | case "FileSearch": | ||
| 6681 | if (oneChild) | ||
| 6682 | { | ||
| 6683 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 6684 | } | ||
| 6685 | oneChild = true; | ||
| 6686 | signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); | ||
| 6687 | id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures | ||
| 6688 | break; | ||
| 6689 | case "FileSearchRef": | ||
| 6690 | if (oneChild) | ||
| 6691 | { | ||
| 6692 | this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); | ||
| 6693 | } | ||
| 6694 | oneChild = true; | ||
| 6695 | var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures | ||
| 6696 | id = new Identifier(AccessModifier.Section, newId); | ||
| 6697 | signature = null; | ||
| 6698 | break; | ||
| 6699 | default: | ||
| 6700 | this.Core.UnexpectedElement(node, child); | ||
| 6701 | break; | ||
| 6702 | } | ||
| 6703 | } | ||
| 6704 | else | ||
| 6705 | { | ||
| 6706 | this.Core.ParseExtensionElement(node, child); | ||
| 6707 | } | ||
| 6708 | } | ||
| 6709 | |||
| 6710 | if (!this.Core.EncounteredError) | ||
| 6711 | { | ||
| 6712 | var symbol = this.Core.AddSymbol(new IniLocatorSymbol(sourceLineNumbers, id) | ||
| 6713 | { | ||
| 6714 | FileName = name, | ||
| 6715 | ShortFileName = shortName, | ||
| 6716 | Section = section, | ||
| 6717 | Key = key, | ||
| 6718 | Type = type | ||
| 6719 | }); | ||
| 6720 | |||
| 6721 | if (CompilerConstants.IntegerNotSet != field) | ||
| 6722 | { | ||
| 6723 | symbol.Field = field; | ||
| 6724 | } | ||
| 6725 | } | ||
| 6726 | |||
| 6727 | return signature; | ||
| 6728 | } | ||
| 6729 | |||
| 6730 | /// <summary> | ||
| 6731 | /// Parses an isolated component element. | ||
| 6732 | /// </summary> | ||
| 6733 | /// <param name="node">Element to parse.</param> | ||
| 6734 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 6735 | private void ParseIsolateComponentElement(XElement node, string componentId) | ||
| 6736 | { | ||
| 6737 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 6738 | string shared = null; | ||
| 6739 | |||
| 6740 | foreach (var attrib in node.Attributes()) | ||
| 6741 | { | ||
| 6742 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 6743 | { | ||
| 6744 | switch (attrib.Name.LocalName) | ||
| 6745 | { | ||
| 6746 | case "Shared": | ||
| 6747 | shared = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 6748 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, shared); | ||
| 6749 | break; | ||
| 6750 | default: | ||
| 6751 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 6752 | break; | ||
| 6753 | } | ||
| 6754 | } | ||
| 6755 | else | ||
| 6756 | { | ||
| 6757 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 6758 | } | ||
| 6759 | } | ||
| 6760 | |||
| 6761 | if (null == shared) | ||
| 6762 | { | ||
| 6763 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Shared")); | ||
| 6764 | } | ||
| 6765 | |||
| 6766 | this.Core.ParseForExtensionElements(node); | ||
| 6767 | |||
| 6768 | if (!this.Core.EncounteredError) | ||
| 6769 | { | ||
| 6770 | this.Core.AddSymbol(new IsolatedComponentSymbol(sourceLineNumbers) | ||
| 6771 | { | ||
| 6772 | SharedComponentRef = shared, | ||
| 6773 | ApplicationComponentRef = componentId | ||
| 6774 | }); | ||
| 6775 | } | ||
| 6776 | } | ||
| 6777 | |||
| 6778 | /// <summary> | ||
| 6779 | /// Parses a PatchCertificates or PackageCertificates element. | ||
| 6780 | /// </summary> | ||
| 6781 | /// <param name="node">The element to parse.</param> | ||
| 6782 | private void ParseCertificatesElement(XElement node) | ||
| 6783 | { | ||
| 6784 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 6785 | |||
| 6786 | // no attributes are supported for this element | ||
| 6787 | foreach (var attrib in node.Attributes()) | ||
| 6788 | { | ||
| 6789 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 6790 | { | ||
| 6791 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 6792 | } | ||
| 6793 | else | ||
| 6794 | { | ||
| 6795 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 6796 | } | ||
| 6797 | } | ||
| 6798 | |||
| 6799 | foreach (var child in node.Elements()) | ||
| 6800 | { | ||
| 6801 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 6802 | { | ||
| 6803 | switch (child.Name.LocalName) | ||
| 6804 | { | ||
| 6805 | case "DigitalCertificate": | ||
| 6806 | var name = this.ParseDigitalCertificateElement(child); | ||
| 6807 | |||
| 6808 | if (!this.Core.EncounteredError) | ||
| 6809 | { | ||
| 6810 | if ("PatchCertificates" == node.Name.LocalName) | ||
| 6811 | { | ||
| 6812 | this.Core.AddSymbol(new MsiPatchCertificateSymbol(sourceLineNumbers) | ||
| 6813 | { | ||
| 6814 | PatchCertificate = name, | ||
| 6815 | DigitalCertificateRef = name, | ||
| 6816 | }); | ||
| 6817 | } | ||
| 6818 | else | ||
| 6819 | { | ||
| 6820 | this.Core.AddSymbol(new MsiPackageCertificateSymbol(sourceLineNumbers) | ||
| 6821 | { | ||
| 6822 | PackageCertificate = name, | ||
| 6823 | DigitalCertificateRef = name, | ||
| 6824 | }); | ||
| 6825 | } | ||
| 6826 | } | ||
| 6827 | break; | ||
| 6828 | default: | ||
| 6829 | this.Core.UnexpectedElement(node, child); | ||
| 6830 | break; | ||
| 6831 | } | ||
| 6832 | } | ||
| 6833 | else | ||
| 6834 | { | ||
| 6835 | this.Core.ParseExtensionElement(node, child); | ||
| 6836 | } | ||
| 6837 | } | ||
| 6838 | } | ||
| 6839 | |||
| 6840 | /// <summary> | ||
| 6841 | /// Parses an digital certificate element. | ||
| 6842 | /// </summary> | ||
| 6843 | /// <param name="node">Element to parse.</param> | ||
| 6844 | /// <returns>The identifier of the certificate.</returns> | ||
| 6845 | private string ParseDigitalCertificateElement(XElement node) | ||
| 6846 | { | ||
| 6847 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 6848 | Identifier id = null; | ||
| 6849 | string sourceFile = null; | ||
| 6850 | |||
| 6851 | foreach (var attrib in node.Attributes()) | ||
| 6852 | { | ||
| 6853 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 6854 | { | ||
| 6855 | switch (attrib.Name.LocalName) | ||
| 6856 | { | ||
| 6857 | case "Id": | ||
| 6858 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 6859 | break; | ||
| 6860 | case "SourceFile": | ||
| 6861 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6862 | break; | ||
| 6863 | default: | ||
| 6864 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 6865 | break; | ||
| 6866 | } | ||
| 6867 | } | ||
| 6868 | else | ||
| 6869 | { | ||
| 6870 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 6871 | } | ||
| 6872 | } | ||
| 6873 | |||
| 6874 | if (null == id) | ||
| 6875 | { | ||
| 6876 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 6877 | id = Identifier.Invalid; | ||
| 6878 | } | ||
| 6879 | else if (40 < id.Id.Length) | ||
| 6880 | { | ||
| 6881 | this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 40)); | ||
| 6882 | |||
| 6883 | // No need to check for modularization problems since DigitalSignature and thus DigitalCertificate | ||
| 6884 | // currently have no usage in merge modules. | ||
| 6885 | } | ||
| 6886 | |||
| 6887 | if (null == sourceFile) | ||
| 6888 | { | ||
| 6889 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
| 6890 | } | ||
| 6891 | |||
| 6892 | this.Core.ParseForExtensionElements(node); | ||
| 6893 | |||
| 6894 | if (!this.Core.EncounteredError) | ||
| 6895 | { | ||
| 6896 | this.Core.AddSymbol(new MsiDigitalCertificateSymbol(sourceLineNumbers, id) | ||
| 6897 | { | ||
| 6898 | CertData = sourceFile | ||
| 6899 | }); | ||
| 6900 | } | ||
| 6901 | |||
| 6902 | return id.Id; | ||
| 6903 | } | ||
| 6904 | |||
| 6905 | /// <summary> | ||
| 6906 | /// Parses an digital signature element. | ||
| 6907 | /// </summary> | ||
| 6908 | /// <param name="node">Element to parse.</param> | ||
| 6909 | /// <param name="diskId">Disk id inherited from parent media.</param> | ||
| 6910 | private void ParseDigitalSignatureElement(XElement node, string diskId) | ||
| 6911 | { | ||
| 6912 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 6913 | string certificateId = null; | ||
| 6914 | string sourceFile = null; | ||
| 6915 | |||
| 6916 | foreach (var attrib in node.Attributes()) | ||
| 6917 | { | ||
| 6918 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 6919 | { | ||
| 6920 | switch (attrib.Name.LocalName) | ||
| 6921 | { | ||
| 6922 | case "SourceFile": | ||
| 6923 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 6924 | break; | ||
| 6925 | default: | ||
| 6926 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 6927 | break; | ||
| 6928 | } | ||
| 6929 | } | ||
| 6930 | else | ||
| 6931 | { | ||
| 6932 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 6933 | } | ||
| 6934 | } | ||
| 6935 | |||
| 6936 | // sanity check for debug to ensure the stream name will not be a problem | ||
| 6937 | if (null != sourceFile) | ||
| 6938 | { | ||
| 6939 | Debug.Assert(62 >= "MsiDigitalSignature.Media.".Length + diskId.Length); | ||
| 6940 | } | ||
| 6941 | |||
| 6942 | foreach (var child in node.Elements()) | ||
| 6943 | { | ||
| 6944 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 6945 | { | ||
| 6946 | switch (child.Name.LocalName) | ||
| 6947 | { | ||
| 6948 | case "DigitalCertificate": | ||
| 6949 | certificateId = this.ParseDigitalCertificateElement(child); | ||
| 6950 | break; | ||
| 6951 | default: | ||
| 6952 | this.Core.UnexpectedElement(node, child); | ||
| 6953 | break; | ||
| 6954 | } | ||
| 6955 | } | ||
| 6956 | else | ||
| 6957 | { | ||
| 6958 | this.Core.ParseExtensionElement(node, child); | ||
| 6959 | } | ||
| 6960 | } | ||
| 6961 | |||
| 6962 | if (null == certificateId) | ||
| 6963 | { | ||
| 6964 | this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "DigitalCertificate")); | ||
| 6965 | } | ||
| 6966 | |||
| 6967 | if (!this.Core.EncounteredError) | ||
| 6968 | { | ||
| 6969 | this.Core.AddSymbol(new MsiDigitalSignatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "Media", diskId)) | ||
| 6970 | { | ||
| 6971 | Table = "Media", | ||
| 6972 | SignObject = diskId, | ||
| 6973 | DigitalCertificateRef = certificateId, | ||
| 6974 | Hash = sourceFile | ||
| 6975 | }); | ||
| 6976 | } | ||
| 6977 | } | ||
| 6978 | |||
| 6979 | /// <summary> | ||
| 6980 | /// Parses a MajorUpgrade element. | ||
| 6981 | /// </summary> | ||
| 6982 | /// <param name="node">The element to parse.</param> | ||
| 6983 | /// <param name="contextValues">The current context.</param> | ||
| 6984 | private void ParseMajorUpgradeElement(XElement node, IDictionary<string, string> contextValues) | ||
| 6985 | { | ||
| 6986 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 6987 | var migrateFeatures = true; | ||
| 6988 | var ignoreRemoveFailure = false; | ||
| 6989 | var allowDowngrades = false; | ||
| 6990 | var allowSameVersionUpgrades = false; | ||
| 6991 | var blockUpgrades = false; | ||
| 6992 | string downgradeErrorMessage = null; | ||
| 6993 | string disallowUpgradeErrorMessage = null; | ||
| 6994 | string removeFeatures = null; | ||
| 6995 | string schedule = null; | ||
| 6996 | |||
| 6997 | var upgradeCode = contextValues["UpgradeCode"]; | ||
| 6998 | if (String.IsNullOrEmpty(upgradeCode)) | ||
| 6999 | { | ||
| 7000 | this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "UpgradeCode", node.Name.LocalName)); | ||
| 7001 | } | ||
| 7002 | |||
| 7003 | var productVersion = contextValues["ProductVersion"]; | ||
| 7004 | if (String.IsNullOrEmpty(productVersion)) | ||
| 7005 | { | ||
| 7006 | this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "Version", node.Name.LocalName)); | ||
| 7007 | } | ||
| 7008 | |||
| 7009 | var productLanguage = contextValues["ProductLanguage"]; | ||
| 7010 | |||
| 7011 | foreach (var attrib in node.Attributes()) | ||
| 7012 | { | ||
| 7013 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7014 | { | ||
| 7015 | switch (attrib.Name.LocalName) | ||
| 7016 | { | ||
| 7017 | case "AllowDowngrades": | ||
| 7018 | allowDowngrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 7019 | break; | ||
| 7020 | case "AllowSameVersionUpgrades": | ||
| 7021 | allowSameVersionUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 7022 | break; | ||
| 7023 | case "Disallow": | ||
| 7024 | blockUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 7025 | break; | ||
| 7026 | case "DowngradeErrorMessage": | ||
| 7027 | downgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7028 | break; | ||
| 7029 | case "DisallowUpgradeErrorMessage": | ||
| 7030 | disallowUpgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7031 | break; | ||
| 7032 | case "MigrateFeatures": | ||
| 7033 | migrateFeatures = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
| 7034 | break; | ||
| 7035 | case "IgnoreLanguage": | ||
| 7036 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 7037 | { | ||
| 7038 | productLanguage = null; | ||
| 7039 | } | ||
| 7040 | break; | ||
| 7041 | case "IgnoreRemoveFailure": | ||
| 7042 | ignoreRemoveFailure = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
| 7043 | break; | ||
| 7044 | case "RemoveFeatures": | ||
| 7045 | removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7046 | break; | ||
| 7047 | case "Schedule": | ||
| 7048 | schedule = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7049 | break; | ||
| 7050 | default: | ||
| 7051 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7052 | break; | ||
| 7053 | } | ||
| 7054 | } | ||
| 7055 | else | ||
| 7056 | { | ||
| 7057 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7058 | } | ||
| 7059 | } | ||
| 7060 | |||
| 7061 | this.Core.ParseForExtensionElements(node); | ||
| 7062 | |||
| 7063 | if (!allowDowngrades && String.IsNullOrEmpty(downgradeErrorMessage)) | ||
| 7064 | { | ||
| 7065 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes", true)); | ||
| 7066 | } | ||
| 7067 | |||
| 7068 | if (allowDowngrades && !String.IsNullOrEmpty(downgradeErrorMessage)) | ||
| 7069 | { | ||
| 7070 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes")); | ||
| 7071 | } | ||
| 7072 | |||
| 7073 | if (allowDowngrades && allowSameVersionUpgrades) | ||
| 7074 | { | ||
| 7075 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AllowSameVersionUpgrades", "AllowDowngrades", "yes")); | ||
| 7076 | } | ||
| 7077 | |||
| 7078 | if (blockUpgrades && String.IsNullOrEmpty(disallowUpgradeErrorMessage)) | ||
| 7079 | { | ||
| 7080 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes", true)); | ||
| 7081 | } | ||
| 7082 | |||
| 7083 | if (!blockUpgrades && !String.IsNullOrEmpty(disallowUpgradeErrorMessage)) | ||
| 7084 | { | ||
| 7085 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes")); | ||
| 7086 | } | ||
| 7087 | |||
| 7088 | if (!this.Core.EncounteredError) | ||
| 7089 | { | ||
| 7090 | // create the row that performs the upgrade (or downgrade) | ||
| 7091 | var symbol = this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) | ||
| 7092 | { | ||
| 7093 | UpgradeCode = upgradeCode, | ||
| 7094 | Remove = removeFeatures, | ||
| 7095 | MigrateFeatures = migrateFeatures, | ||
| 7096 | IgnoreRemoveFailures = ignoreRemoveFailure, | ||
| 7097 | ActionProperty = WixUpgradeConstants.UpgradeDetectedProperty | ||
| 7098 | }); | ||
| 7099 | |||
| 7100 | if (allowDowngrades) | ||
| 7101 | { | ||
| 7102 | symbol.VersionMin = "0"; | ||
| 7103 | symbol.Language = productLanguage; | ||
| 7104 | symbol.VersionMinInclusive = true; | ||
| 7105 | } | ||
| 7106 | else | ||
| 7107 | { | ||
| 7108 | symbol.VersionMax = productVersion; | ||
| 7109 | symbol.Language = productLanguage; | ||
| 7110 | symbol.VersionMaxInclusive = allowSameVersionUpgrades; | ||
| 7111 | } | ||
| 7112 | |||
| 7113 | // Add launch condition that blocks upgrades | ||
| 7114 | if (blockUpgrades) | ||
| 7115 | { | ||
| 7116 | this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) | ||
| 7117 | { | ||
| 7118 | Condition = WixUpgradeConstants.UpgradePreventedCondition, | ||
| 7119 | Description = downgradeErrorMessage | ||
| 7120 | }); | ||
| 7121 | } | ||
| 7122 | |||
| 7123 | // now create the Upgrade row and launch conditions to prevent downgrades (unless explicitly permitted) | ||
| 7124 | if (!allowDowngrades) | ||
| 7125 | { | ||
| 7126 | this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) | ||
| 7127 | { | ||
| 7128 | UpgradeCode = upgradeCode, | ||
| 7129 | VersionMin = productVersion, | ||
| 7130 | Language = productLanguage, | ||
| 7131 | OnlyDetect = true, | ||
| 7132 | IgnoreRemoveFailures = ignoreRemoveFailure, | ||
| 7133 | ActionProperty = WixUpgradeConstants.DowngradeDetectedProperty | ||
| 7134 | }); | ||
| 7135 | |||
| 7136 | this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) | ||
| 7137 | { | ||
| 7138 | Condition = WixUpgradeConstants.DowngradePreventedCondition, | ||
| 7139 | Description = downgradeErrorMessage | ||
| 7140 | }); | ||
| 7141 | } | ||
| 7142 | |||
| 7143 | // finally, schedule RemoveExistingProducts | ||
| 7144 | string after = null; | ||
| 7145 | switch (schedule) | ||
| 7146 | { | ||
| 7147 | case null: | ||
| 7148 | case "afterInstallValidate": | ||
| 7149 | after = "InstallValidate"; | ||
| 7150 | break; | ||
| 7151 | case "afterInstallInitialize": | ||
| 7152 | after = "InstallInitialize"; | ||
| 7153 | break; | ||
| 7154 | case "afterInstallExecute": | ||
| 7155 | after = "InstallExecute"; | ||
| 7156 | break; | ||
| 7157 | case "afterInstallExecuteAgain": | ||
| 7158 | after = "InstallExecuteAgain"; | ||
| 7159 | break; | ||
| 7160 | case "afterInstallFinalize": | ||
| 7161 | after = "InstallFinalize"; | ||
| 7162 | break; | ||
| 7163 | } | ||
| 7164 | |||
| 7165 | this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, SequenceTable.InstallExecuteSequence, "RemoveExistingProducts", afterAction: after); | ||
| 7166 | } | ||
| 7167 | } | ||
| 7168 | |||
| 7169 | /// <summary> | ||
| 7170 | /// Parses a media element. | ||
| 7171 | /// </summary> | ||
| 7172 | /// <param name="node">Element to parse.</param> | ||
| 7173 | /// <param name="patchId">Set to the PatchId if parsing Patch/Media element otherwise null.</param> | ||
| 7174 | private void ParseMediaElement(XElement node, string patchId) | ||
| 7175 | { | ||
| 7176 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 7177 | var id = CompilerConstants.IntegerNotSet; | ||
| 7178 | string cabinet = null; | ||
| 7179 | CompressionLevel? compressionLevel = null; | ||
| 7180 | string diskPrompt = null; | ||
| 7181 | string layout = null; | ||
| 7182 | var patch = null != patchId; | ||
| 7183 | string volumeLabel = null; | ||
| 7184 | string source = null; | ||
| 7185 | string symbols = null; | ||
| 7186 | |||
| 7187 | var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; | ||
| 7188 | |||
| 7189 | foreach (var attrib in node.Attributes()) | ||
| 7190 | { | ||
| 7191 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7192 | { | ||
| 7193 | switch (attrib.Name.LocalName) | ||
| 7194 | { | ||
| 7195 | case "Id": | ||
| 7196 | id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 7197 | break; | ||
| 7198 | case "Cabinet": | ||
| 7199 | cabinet = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7200 | break; | ||
| 7201 | case "CompressionLevel": | ||
| 7202 | compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); | ||
| 7203 | break; | ||
| 7204 | case "DiskPrompt": | ||
| 7205 | diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7206 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined | ||
| 7207 | break; | ||
| 7208 | case "EmbedCab": | ||
| 7209 | embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 7210 | break; | ||
| 7211 | case "Layout": | ||
| 7212 | layout = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7213 | break; | ||
| 7214 | case "VolumeLabel": | ||
| 7215 | volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7216 | break; | ||
| 7217 | case "Source": | ||
| 7218 | source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7219 | break; | ||
| 7220 | default: | ||
| 7221 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7222 | break; | ||
| 7223 | } | ||
| 7224 | } | ||
| 7225 | else | ||
| 7226 | { | ||
| 7227 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7228 | } | ||
| 7229 | } | ||
| 7230 | |||
| 7231 | if (CompilerConstants.IntegerNotSet == id) | ||
| 7232 | { | ||
| 7233 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 7234 | id = CompilerConstants.IllegalInteger; | ||
| 7235 | } | ||
| 7236 | |||
| 7237 | if (YesNoType.IllegalValue != embedCab) | ||
| 7238 | { | ||
| 7239 | if (YesNoType.Yes == embedCab) | ||
| 7240 | { | ||
| 7241 | if (null == cabinet) | ||
| 7242 | { | ||
| 7243 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "EmbedCab", "yes")); | ||
| 7244 | } | ||
| 7245 | else | ||
| 7246 | { | ||
| 7247 | if (62 < cabinet.Length) | ||
| 7248 | { | ||
| 7249 | this.Core.Write(ErrorMessages.MediaEmbeddedCabinetNameTooLong(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet, cabinet.Length)); | ||
| 7250 | } | ||
| 7251 | |||
| 7252 | cabinet = String.Concat("#", cabinet); | ||
| 7253 | } | ||
| 7254 | } | ||
| 7255 | else // external cabinet file | ||
| 7256 | { | ||
| 7257 | // external cabinet files must use 8.3 filenames | ||
| 7258 | if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidLongFilename(cabinet) && !Common.ContainsValidBinderVariable(cabinet)) | ||
| 7259 | { | ||
| 7260 | this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet)); | ||
| 7261 | } | ||
| 7262 | } | ||
| 7263 | } | ||
| 7264 | |||
| 7265 | if (compressionLevel.HasValue && String.IsNullOrEmpty(cabinet)) | ||
| 7266 | { | ||
| 7267 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "CompressionLevel")); | ||
| 7268 | } | ||
| 7269 | |||
| 7270 | if (patch) | ||
| 7271 | { | ||
| 7272 | // Default Source to a form of the Patch Id if none is specified. | ||
| 7273 | if (null == source) | ||
| 7274 | { | ||
| 7275 | source = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); | ||
| 7276 | } | ||
| 7277 | } | ||
| 7278 | |||
| 7279 | foreach (var child in node.Elements()) | ||
| 7280 | { | ||
| 7281 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 7282 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 7283 | { | ||
| 7284 | switch (child.Name.LocalName) | ||
| 7285 | { | ||
| 7286 | case "DigitalSignature": | ||
| 7287 | if (YesNoType.Yes == embedCab) | ||
| 7288 | { | ||
| 7289 | this.Core.Write(ErrorMessages.SignedEmbeddedCabinet(childSourceLineNumbers)); | ||
| 7290 | } | ||
| 7291 | else if (null == cabinet) | ||
| 7292 | { | ||
| 7293 | this.Core.Write(ErrorMessages.ExpectedSignedCabinetName(childSourceLineNumbers)); | ||
| 7294 | } | ||
| 7295 | else | ||
| 7296 | { | ||
| 7297 | this.ParseDigitalSignatureElement(child, id.ToString(CultureInfo.InvariantCulture.NumberFormat)); | ||
| 7298 | } | ||
| 7299 | break; | ||
| 7300 | case "PatchBaseline": | ||
| 7301 | if (patch) | ||
| 7302 | { | ||
| 7303 | this.ParsePatchBaselineElement(child, id); | ||
| 7304 | } | ||
| 7305 | else | ||
| 7306 | { | ||
| 7307 | this.Core.UnexpectedElement(node, child); | ||
| 7308 | } | ||
| 7309 | break; | ||
| 7310 | case "SymbolPath": | ||
| 7311 | if (null != symbols) | ||
| 7312 | { | ||
| 7313 | symbols += "" + this.ParseSymbolPathElement(child); | ||
| 7314 | } | ||
| 7315 | else | ||
| 7316 | { | ||
| 7317 | symbols = this.ParseSymbolPathElement(child); | ||
| 7318 | } | ||
| 7319 | break; | ||
| 7320 | default: | ||
| 7321 | this.Core.UnexpectedElement(node, child); | ||
| 7322 | break; | ||
| 7323 | } | ||
| 7324 | } | ||
| 7325 | else | ||
| 7326 | { | ||
| 7327 | this.Core.ParseExtensionElement(node, child); | ||
| 7328 | } | ||
| 7329 | } | ||
| 7330 | |||
| 7331 | // add the row to the section | ||
| 7332 | if (!this.Core.EncounteredError) | ||
| 7333 | { | ||
| 7334 | this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) | ||
| 7335 | { | ||
| 7336 | DiskId = id, | ||
| 7337 | DiskPrompt = diskPrompt, | ||
| 7338 | Cabinet = cabinet, | ||
| 7339 | VolumeLabel = volumeLabel, | ||
| 7340 | Source = source, // the Source column is only set when creating a patch | ||
| 7341 | CompressionLevel = compressionLevel, | ||
| 7342 | Layout = layout | ||
| 7343 | }); | ||
| 7344 | |||
| 7345 | if (null != symbols) | ||
| 7346 | { | ||
| 7347 | this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Media, id)) | ||
| 7348 | { | ||
| 7349 | SymbolType = SymbolPathType.Media, | ||
| 7350 | SymbolId = id.ToString(CultureInfo.InvariantCulture), | ||
| 7351 | SymbolPaths = symbols | ||
| 7352 | }); | ||
| 7353 | } | ||
| 7354 | } | ||
| 7355 | } | ||
| 7356 | |||
| 7357 | /// <summary> | ||
| 7358 | /// Parses a media template element. | ||
| 7359 | /// </summary> | ||
| 7360 | /// <param name="node">Element to parse.</param> | ||
| 7361 | /// <param name="patchId">Set to the PatchId if parsing Patch/Media element otherwise null.</param> | ||
| 7362 | private void ParseMediaTemplateElement(XElement node, string patchId) | ||
| 7363 | { | ||
| 7364 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 7365 | var cabinetTemplate = "cab{0}.cab"; | ||
| 7366 | string diskPrompt = null; | ||
| 7367 | var patch = null != patchId; | ||
| 7368 | string volumeLabel = null; | ||
| 7369 | int? maximumUncompressedMediaSize = null; | ||
| 7370 | int? maximumCabinetSizeForLargeFileSplitting = null; | ||
| 7371 | CompressionLevel? compressionLevel = null; // this defaults to 'medium' in the MSI and Burn backends | ||
| 7372 | |||
| 7373 | var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; | ||
| 7374 | |||
| 7375 | foreach (var attrib in node.Attributes()) | ||
| 7376 | { | ||
| 7377 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7378 | { | ||
| 7379 | switch (attrib.Name.LocalName) | ||
| 7380 | { | ||
| 7381 | case "CabinetTemplate": | ||
| 7382 | var authoredCabinetTemplateValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 7383 | if (!String.IsNullOrEmpty(authoredCabinetTemplateValue)) | ||
| 7384 | { | ||
| 7385 | cabinetTemplate = authoredCabinetTemplateValue; | ||
| 7386 | } | ||
| 7387 | |||
| 7388 | // Create an example cabinet name using the maximum number of cabinets supported, 999. | ||
| 7389 | var exampleCabinetName = String.Format(cabinetTemplate, "###"); | ||
| 7390 | if (!this.Core.IsValidLocIdentifier(exampleCabinetName)) | ||
| 7391 | { | ||
| 7392 | // The example name should not match the authored template since that would nullify the | ||
| 7393 | // reason for having multiple cabinets. External cabinet files must also be valid file names. | ||
| 7394 | if (exampleCabinetName.Equals(authoredCabinetTemplateValue, StringComparison.OrdinalIgnoreCase) || !this.Core.IsValidLongFilename(exampleCabinetName, false)) | ||
| 7395 | { | ||
| 7396 | this.Core.Write(ErrorMessages.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate)); | ||
| 7397 | } | ||
| 7398 | else if (!this.Core.IsValidLongFilename(exampleCabinetName) && !Common.ContainsValidBinderVariable(exampleCabinetName)) // ignore short names with wix variables because it rarely works out. | ||
| 7399 | { | ||
| 7400 | this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate)); | ||
| 7401 | } | ||
| 7402 | } | ||
| 7403 | break; | ||
| 7404 | case "CompressionLevel": | ||
| 7405 | compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); | ||
| 7406 | break; | ||
| 7407 | case "DiskPrompt": | ||
| 7408 | diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7409 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined | ||
| 7410 | this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
| 7411 | break; | ||
| 7412 | case "EmbedCab": | ||
| 7413 | embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 7414 | break; | ||
| 7415 | case "VolumeLabel": | ||
| 7416 | volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7417 | this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
| 7418 | break; | ||
| 7419 | case "MaximumUncompressedMediaSize": | ||
| 7420 | maximumUncompressedMediaSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); | ||
| 7421 | break; | ||
| 7422 | case "MaximumCabinetSizeForLargeFileSplitting": | ||
| 7423 | maximumCabinetSizeForLargeFileSplitting = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Compiler.MinValueOfMaxCabSizeForLargeFileSplitting, Compiler.MaxValueOfMaxCabSizeForLargeFileSplitting); | ||
| 7424 | break; | ||
| 7425 | default: | ||
| 7426 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7427 | break; | ||
| 7428 | } | ||
| 7429 | } | ||
| 7430 | else | ||
| 7431 | { | ||
| 7432 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7433 | } | ||
| 7434 | } | ||
| 7435 | |||
| 7436 | if (YesNoType.Yes == embedCab) | ||
| 7437 | { | ||
| 7438 | cabinetTemplate = String.Concat("#", cabinetTemplate); | ||
| 7439 | } | ||
| 7440 | |||
| 7441 | if (!this.Core.EncounteredError) | ||
| 7442 | { | ||
| 7443 | this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, 1)) | ||
| 7444 | { | ||
| 7445 | DiskId = 1 | ||
| 7446 | }); | ||
| 7447 | |||
| 7448 | this.Core.AddSymbol(new WixMediaTemplateSymbol(sourceLineNumbers) | ||
| 7449 | { | ||
| 7450 | CabinetTemplate = cabinetTemplate, | ||
| 7451 | VolumeLabel = volumeLabel, | ||
| 7452 | DiskPrompt = diskPrompt, | ||
| 7453 | MaximumUncompressedMediaSize = maximumUncompressedMediaSize, | ||
| 7454 | MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting, | ||
| 7455 | CompressionLevel = compressionLevel | ||
| 7456 | }); | ||
| 7457 | |||
| 7458 | //else | ||
| 7459 | //{ | ||
| 7460 | // mediaTemplateRow.MaximumUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize; | ||
| 7461 | //} | ||
| 7462 | |||
| 7463 | //else | ||
| 7464 | //{ | ||
| 7465 | // mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = 0; // Default value of 0 corresponds to max size of 2048 MB (i.e. 2 GB) | ||
| 7466 | //} | ||
| 7467 | } | ||
| 7468 | } | ||
| 7469 | |||
| 7470 | /// <summary> | ||
| 7471 | /// Parses a merge element. | ||
| 7472 | /// </summary> | ||
| 7473 | /// <param name="node">Element to parse.</param> | ||
| 7474 | /// <param name="directoryId">Identifier for parent directory.</param> | ||
| 7475 | /// <param name="diskId">Disk id inherited from parent directory.</param> | ||
| 7476 | private void ParseMergeElement(XElement node, string directoryId, int diskId) | ||
| 7477 | { | ||
| 7478 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 7479 | Identifier id = null; | ||
| 7480 | var configData = String.Empty; | ||
| 7481 | FileSymbolAttributes attributes = 0; | ||
| 7482 | string language = null; | ||
| 7483 | string sourceFile = null; | ||
| 7484 | |||
| 7485 | foreach (var attrib in node.Attributes()) | ||
| 7486 | { | ||
| 7487 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7488 | { | ||
| 7489 | switch (attrib.Name.LocalName) | ||
| 7490 | { | ||
| 7491 | case "Id": | ||
| 7492 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 7493 | break; | ||
| 7494 | case "DiskId": | ||
| 7495 | diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 7496 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); | ||
| 7497 | break; | ||
| 7498 | case "FileCompression": | ||
| 7499 | var compress = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 7500 | attributes |= compress == YesNoType.Yes ? FileSymbolAttributes.Compressed : 0; | ||
| 7501 | attributes |= compress == YesNoType.No ? FileSymbolAttributes.Uncompressed : 0; | ||
| 7502 | break; | ||
| 7503 | case "Language": | ||
| 7504 | language = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 7505 | break; | ||
| 7506 | case "SourceFile": | ||
| 7507 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7508 | break; | ||
| 7509 | default: | ||
| 7510 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7511 | break; | ||
| 7512 | } | ||
| 7513 | } | ||
| 7514 | else | ||
| 7515 | { | ||
| 7516 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7517 | } | ||
| 7518 | } | ||
| 7519 | |||
| 7520 | if (null == id) | ||
| 7521 | { | ||
| 7522 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 7523 | } | ||
| 7524 | |||
| 7525 | if (null == language) | ||
| 7526 | { | ||
| 7527 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); | ||
| 7528 | } | ||
| 7529 | |||
| 7530 | if (null == sourceFile) | ||
| 7531 | { | ||
| 7532 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
| 7533 | } | ||
| 7534 | |||
| 7535 | foreach (var child in node.Elements()) | ||
| 7536 | { | ||
| 7537 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 7538 | { | ||
| 7539 | switch (child.Name.LocalName) | ||
| 7540 | { | ||
| 7541 | case "ConfigurationData": | ||
| 7542 | if (0 == configData.Length) | ||
| 7543 | { | ||
| 7544 | configData = this.ParseConfigurationDataElement(child); | ||
| 7545 | } | ||
| 7546 | else | ||
| 7547 | { | ||
| 7548 | configData = String.Concat(configData, ",", this.ParseConfigurationDataElement(child)); | ||
| 7549 | } | ||
| 7550 | break; | ||
| 7551 | default: | ||
| 7552 | this.Core.UnexpectedElement(node, child); | ||
| 7553 | break; | ||
| 7554 | } | ||
| 7555 | } | ||
| 7556 | else | ||
| 7557 | { | ||
| 7558 | this.Core.ParseExtensionElement(node, child); | ||
| 7559 | } | ||
| 7560 | } | ||
| 7561 | |||
| 7562 | if (!this.Core.EncounteredError) | ||
| 7563 | { | ||
| 7564 | var symbol = this.Core.AddSymbol(new WixMergeSymbol(sourceLineNumbers, id) | ||
| 7565 | { | ||
| 7566 | DirectoryRef = directoryId, | ||
| 7567 | SourceFile = sourceFile, | ||
| 7568 | DiskId = diskId, | ||
| 7569 | ConfigurationData = configData, | ||
| 7570 | FileAttributes = attributes, | ||
| 7571 | FeatureRef = Guid.Empty.ToString("B") | ||
| 7572 | }); | ||
| 7573 | |||
| 7574 | symbol.Set((int)WixMergeSymbolFields.Language, language); | ||
| 7575 | } | ||
| 7576 | } | ||
| 7577 | |||
| 7578 | /// <summary> | ||
| 7579 | /// Parses a standard directory element. | ||
| 7580 | /// </summary> | ||
| 7581 | /// <param name="node">Element to parse.</param> | ||
| 7582 | private void ParseStandardDirectoryElement(XElement node) | ||
| 7583 | { | ||
| 7584 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 7585 | string id = null; | ||
| 7586 | |||
| 7587 | foreach (var attrib in node.Attributes()) | ||
| 7588 | { | ||
| 7589 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7590 | { | ||
| 7591 | switch (attrib.Name.LocalName) | ||
| 7592 | { | ||
| 7593 | case "Id": | ||
| 7594 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 7595 | break; | ||
| 7596 | default: | ||
| 7597 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7598 | break; | ||
| 7599 | } | ||
| 7600 | } | ||
| 7601 | else | ||
| 7602 | { | ||
| 7603 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7604 | } | ||
| 7605 | } | ||
| 7606 | |||
| 7607 | if (String.IsNullOrEmpty(id)) | ||
| 7608 | { | ||
| 7609 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 7610 | } | ||
| 7611 | else if (!WindowsInstallerStandard.IsStandardDirectory(id)) | ||
| 7612 | { | ||
| 7613 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Id", id, String.Join(", \"", WindowsInstallerStandard.StandardDirectories().Select(d => d.Id.Id)))); | ||
| 7614 | } | ||
| 7615 | |||
| 7616 | foreach (var child in node.Elements()) | ||
| 7617 | { | ||
| 7618 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 7619 | { | ||
| 7620 | switch (child.Name.LocalName) | ||
| 7621 | { | ||
| 7622 | case "Component": | ||
| 7623 | this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId: CompilerConstants.IntegerNotSet, id, srcPath: String.Empty); | ||
| 7624 | break; | ||
| 7625 | case "Directory": | ||
| 7626 | this.ParseDirectoryElement(child, id, diskId: CompilerConstants.IntegerNotSet, fileSource: String.Empty); | ||
| 7627 | break; | ||
| 7628 | case "Merge": | ||
| 7629 | this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet); | ||
| 7630 | break; | ||
| 7631 | default: | ||
| 7632 | this.Core.UnexpectedElement(node, child); | ||
| 7633 | break; | ||
| 7634 | } | ||
| 7635 | } | ||
| 7636 | else | ||
| 7637 | { | ||
| 7638 | this.Core.ParseExtensionElement(node, child); | ||
| 7639 | } | ||
| 7640 | } | ||
| 7641 | } | ||
| 7642 | |||
| 7643 | /// <summary> | ||
| 7644 | /// Parses a configuration data element. | ||
| 7645 | /// </summary> | ||
| 7646 | /// <param name="node">Element to parse.</param> | ||
| 7647 | /// <returns>String in format "name=value" with '%', ',' and '=' hex encoded.</returns> | ||
| 7648 | private string ParseConfigurationDataElement(XElement node) | ||
| 7649 | { | ||
| 7650 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 7651 | string name = null; | ||
| 7652 | string value = null; | ||
| 7653 | |||
| 7654 | foreach (var attrib in node.Attributes()) | ||
| 7655 | { | ||
| 7656 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7657 | { | ||
| 7658 | switch (attrib.Name.LocalName) | ||
| 7659 | { | ||
| 7660 | case "Name": | ||
| 7661 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7662 | break; | ||
| 7663 | case "Value": | ||
| 7664 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7665 | break; | ||
| 7666 | default: | ||
| 7667 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7668 | break; | ||
| 7669 | } | ||
| 7670 | } | ||
| 7671 | else | ||
| 7672 | { | ||
| 7673 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7674 | } | ||
| 7675 | } | ||
| 7676 | |||
| 7677 | if (null == name) | ||
| 7678 | { | ||
| 7679 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 7680 | } | ||
| 7681 | else // need to hex encode these characters | ||
| 7682 | { | ||
| 7683 | name = name.Replace("%", "%25"); | ||
| 7684 | name = name.Replace("=", "%3D"); | ||
| 7685 | name = name.Replace(",", "%2C"); | ||
| 7686 | } | ||
| 7687 | |||
| 7688 | if (null == value) | ||
| 7689 | { | ||
| 7690 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 7691 | } | ||
| 7692 | else // need to hex encode these characters | ||
| 7693 | { | ||
| 7694 | value = value.Replace("%", "%25"); | ||
| 7695 | value = value.Replace("=", "%3D"); | ||
| 7696 | value = value.Replace(",", "%2C"); | ||
| 7697 | } | ||
| 7698 | |||
| 7699 | this.Core.ParseForExtensionElements(node); | ||
| 7700 | |||
| 7701 | return String.Concat(name, "=", value); | ||
| 7702 | } | ||
| 7703 | |||
| 7704 | /// <summary> | ||
| 7705 | /// Parses a Level element. | ||
| 7706 | /// </summary> | ||
| 7707 | /// <param name="node">Element to parse.</param> | ||
| 7708 | /// <param name="featureId">Id of the parent Feature element.</param> | ||
| 7709 | private void ParseLevelElement(XElement node, string featureId) | ||
| 7710 | { | ||
| 7711 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 7712 | string condition = null; | ||
| 7713 | int? level = null; | ||
| 7714 | |||
| 7715 | foreach (var attrib in node.Attributes()) | ||
| 7716 | { | ||
| 7717 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7718 | { | ||
| 7719 | switch (attrib.Name.LocalName) | ||
| 7720 | { | ||
| 7721 | case "Condition": | ||
| 7722 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7723 | break; | ||
| 7724 | case "Value": | ||
| 7725 | level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 7726 | break; | ||
| 7727 | default: | ||
| 7728 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7729 | break; | ||
| 7730 | } | ||
| 7731 | } | ||
| 7732 | else | ||
| 7733 | { | ||
| 7734 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7735 | } | ||
| 7736 | } | ||
| 7737 | |||
| 7738 | if (!level.HasValue) | ||
| 7739 | { | ||
| 7740 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); | ||
| 7741 | } | ||
| 7742 | |||
| 7743 | if (String.IsNullOrEmpty(condition)) | ||
| 7744 | { | ||
| 7745 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); | ||
| 7746 | } | ||
| 7747 | |||
| 7748 | this.Core.ParseForExtensionElements(node); | ||
| 7749 | |||
| 7750 | if (!this.Core.EncounteredError) | ||
| 7751 | { | ||
| 7752 | if (CompilerConstants.IntegerNotSet == level) | ||
| 7753 | { | ||
| 7754 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); | ||
| 7755 | level = CompilerConstants.IllegalInteger; | ||
| 7756 | } | ||
| 7757 | |||
| 7758 | if (!this.Core.EncounteredError) | ||
| 7759 | { | ||
| 7760 | this.Core.AddSymbol(new ConditionSymbol(sourceLineNumbers) | ||
| 7761 | { | ||
| 7762 | FeatureRef = featureId, | ||
| 7763 | Level = level.Value, | ||
| 7764 | Condition = condition | ||
| 7765 | }); | ||
| 7766 | } | ||
| 7767 | } | ||
| 7768 | } | ||
| 7769 | |||
| 7770 | /// <summary> | ||
| 7771 | /// Parses a merge reference element. | ||
| 7772 | /// </summary> | ||
| 7773 | /// <param name="node">Element to parse.</param> | ||
| 7774 | /// <param name="parentType">Parents complex reference type.</param> | ||
| 7775 | /// <param name="parentId">Identifier for parent feature or feature group.</param> | ||
| 7776 | private void ParseMergeRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 7777 | { | ||
| 7778 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 7779 | string id = null; | ||
| 7780 | var primary = YesNoType.NotSet; | ||
| 7781 | |||
| 7782 | foreach (var attrib in node.Attributes()) | ||
| 7783 | { | ||
| 7784 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7785 | { | ||
| 7786 | switch (attrib.Name.LocalName) | ||
| 7787 | { | ||
| 7788 | case "Id": | ||
| 7789 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 7790 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixMerge, id); | ||
| 7791 | break; | ||
| 7792 | case "Primary": | ||
| 7793 | primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 7794 | break; | ||
| 7795 | default: | ||
| 7796 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7797 | break; | ||
| 7798 | } | ||
| 7799 | } | ||
| 7800 | else | ||
| 7801 | { | ||
| 7802 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7803 | } | ||
| 7804 | } | ||
| 7805 | |||
| 7806 | if (null == id) | ||
| 7807 | { | ||
| 7808 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 7809 | } | ||
| 7810 | |||
| 7811 | this.Core.ParseForExtensionElements(node); | ||
| 7812 | |||
| 7813 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Module, id, (YesNoType.Yes == primary)); | ||
| 7814 | } | ||
| 7815 | |||
| 7816 | /// <summary> | ||
| 7817 | /// Parses a mime element. | ||
| 7818 | /// </summary> | ||
| 7819 | /// <param name="node">Element to parse.</param> | ||
| 7820 | /// <param name="extension">Identifier for parent extension.</param> | ||
| 7821 | /// <param name="componentId">Identifier for parent component.</param> | ||
| 7822 | /// <param name="parentAdvertised">Flag if the parent element is advertised.</param> | ||
| 7823 | /// <returns>Content type if this is the default for the MIME type.</returns> | ||
| 7824 | private string ParseMIMEElement(XElement node, string extension, string componentId, YesNoType parentAdvertised) | ||
| 7825 | { | ||
| 7826 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 7827 | string classId = null; | ||
| 7828 | string contentType = null; | ||
| 7829 | var advertise = parentAdvertised; | ||
| 7830 | var returnContentType = YesNoType.NotSet; | ||
| 7831 | |||
| 7832 | foreach (var attrib in node.Attributes()) | ||
| 7833 | { | ||
| 7834 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7835 | { | ||
| 7836 | switch (attrib.Name.LocalName) | ||
| 7837 | { | ||
| 7838 | case "Advertise": | ||
| 7839 | advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 7840 | break; | ||
| 7841 | case "Class": | ||
| 7842 | classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 7843 | break; | ||
| 7844 | case "ContentType": | ||
| 7845 | contentType = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7846 | break; | ||
| 7847 | case "Default": | ||
| 7848 | returnContentType = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 7849 | break; | ||
| 7850 | default: | ||
| 7851 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7852 | break; | ||
| 7853 | } | ||
| 7854 | } | ||
| 7855 | else | ||
| 7856 | { | ||
| 7857 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7858 | } | ||
| 7859 | } | ||
| 7860 | |||
| 7861 | if (null == contentType) | ||
| 7862 | { | ||
| 7863 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ContentType")); | ||
| 7864 | } | ||
| 7865 | |||
| 7866 | // if the advertise state has not been set, default to non-advertised | ||
| 7867 | if (YesNoType.NotSet == advertise) | ||
| 7868 | { | ||
| 7869 | advertise = YesNoType.No; | ||
| 7870 | } | ||
| 7871 | |||
| 7872 | this.Core.ParseForExtensionElements(node); | ||
| 7873 | |||
| 7874 | if (YesNoType.Yes == advertise) | ||
| 7875 | { | ||
| 7876 | if (YesNoType.Yes != parentAdvertised) | ||
| 7877 | { | ||
| 7878 | this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), parentAdvertised.ToString())); | ||
| 7879 | } | ||
| 7880 | |||
| 7881 | if (!this.Core.EncounteredError) | ||
| 7882 | { | ||
| 7883 | this.Core.AddSymbol(new MIMESymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, contentType)) | ||
| 7884 | { | ||
| 7885 | ContentType = contentType, | ||
| 7886 | ExtensionRef = extension, | ||
| 7887 | CLSID = classId | ||
| 7888 | }); | ||
| 7889 | } | ||
| 7890 | } | ||
| 7891 | else if (YesNoType.No == advertise) | ||
| 7892 | { | ||
| 7893 | if (YesNoType.Yes == returnContentType && YesNoType.Yes == parentAdvertised) | ||
| 7894 | { | ||
| 7895 | this.Core.Write(ErrorMessages.CannotDefaultMismatchedAdvertiseStates(sourceLineNumbers)); | ||
| 7896 | } | ||
| 7897 | |||
| 7898 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "Extension", String.Concat(".", extension), componentId); | ||
| 7899 | if (null != classId) | ||
| 7900 | { | ||
| 7901 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "CLSID", classId, componentId); | ||
| 7902 | } | ||
| 7903 | } | ||
| 7904 | |||
| 7905 | return YesNoType.Yes == returnContentType ? contentType : null; | ||
| 7906 | } | ||
| 7907 | |||
| 7908 | /// <summary> | ||
| 7909 | /// Parses a patch property element. | ||
| 7910 | /// </summary> | ||
| 7911 | /// <param name="node">The element to parse.</param> | ||
| 7912 | /// <param name="patch">True if parsing an patch element.</param> | ||
| 7913 | private void ParsePatchPropertyElement(XElement node, bool patch) | ||
| 7914 | { | ||
| 7915 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 7916 | string name = null; | ||
| 7917 | string company = null; | ||
| 7918 | string value = null; | ||
| 7919 | |||
| 7920 | foreach (var attrib in node.Attributes()) | ||
| 7921 | { | ||
| 7922 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 7923 | { | ||
| 7924 | switch (attrib.Name.LocalName) | ||
| 7925 | { | ||
| 7926 | case "Id": | ||
| 7927 | case "Name": | ||
| 7928 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7929 | break; | ||
| 7930 | case "Company": | ||
| 7931 | company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7932 | break; | ||
| 7933 | case "Value": | ||
| 7934 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 7935 | break; | ||
| 7936 | default: | ||
| 7937 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 7938 | break; | ||
| 7939 | } | ||
| 7940 | } | ||
| 7941 | else | ||
| 7942 | { | ||
| 7943 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 7944 | } | ||
| 7945 | } | ||
| 7946 | |||
| 7947 | if (null == name) | ||
| 7948 | { | ||
| 7949 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 7950 | } | ||
| 7951 | |||
| 7952 | if (null == value) | ||
| 7953 | { | ||
| 7954 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 7955 | } | ||
| 7956 | |||
| 7957 | this.Core.ParseForExtensionElements(node); | ||
| 7958 | |||
| 7959 | if (patch) | ||
| 7960 | { | ||
| 7961 | // /Patch/PatchProperty goes directly into MsiPatchMetadata table | ||
| 7962 | this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, company, name)) | ||
| 7963 | { | ||
| 7964 | Company = company, | ||
| 7965 | Property = name, | ||
| 7966 | Value = value | ||
| 7967 | }); | ||
| 7968 | } | ||
| 7969 | else | ||
| 7970 | { | ||
| 7971 | if (null != company) | ||
| 7972 | { | ||
| 7973 | this.Core.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); | ||
| 7974 | } | ||
| 7975 | this.AddPrivateProperty(sourceLineNumbers, name, value); | ||
| 7976 | } | ||
| 7977 | } | ||
| 7978 | |||
| 7979 | /// <summary> | ||
| 7980 | /// Adds a row to the properties table. | ||
| 7981 | /// </summary> | ||
| 7982 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
| 7983 | /// <param name="name">Name of the property.</param> | ||
| 7984 | /// <param name="value">Value of the property.</param> | ||
| 7985 | private void AddPrivateProperty(SourceLineNumber sourceLineNumbers, string name, string value) | ||
| 7986 | { | ||
| 7987 | if (!this.Core.EncounteredError) | ||
| 7988 | { | ||
| 7989 | this.Core.AddSymbol(new PropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) | ||
| 7990 | { | ||
| 7991 | Value = value | ||
| 7992 | }); | ||
| 7993 | } | ||
| 7994 | } | ||
| 7995 | |||
| 7996 | /// <summary> | ||
| 7997 | /// Parses a TargetProductCode element. | ||
| 7998 | /// </summary> | ||
| 7999 | /// <param name="node">The element to parse.</param> | ||
| 8000 | /// <returns>The id from the node.</returns> | ||
| 8001 | private string ParseTargetProductCodeElement(XElement node) | ||
| 8002 | { | ||
| 8003 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 8004 | string id = null; | ||
| 8005 | |||
| 8006 | foreach (var attrib in node.Attributes()) | ||
| 8007 | { | ||
| 8008 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 8009 | { | ||
| 8010 | switch (attrib.Name.LocalName) | ||
| 8011 | { | ||
| 8012 | case "Id": | ||
| 8013 | id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 8014 | if (id.Length > 0 && "*" != id) | ||
| 8015 | { | ||
| 8016 | id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 8017 | } | ||
| 8018 | break; | ||
| 8019 | default: | ||
| 8020 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 8021 | break; | ||
| 8022 | } | ||
| 8023 | } | ||
| 8024 | else | ||
| 8025 | { | ||
| 8026 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 8027 | } | ||
| 8028 | } | ||
| 8029 | |||
| 8030 | if (null == id) | ||
| 8031 | { | ||
| 8032 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 8033 | } | ||
| 8034 | |||
| 8035 | this.Core.ParseForExtensionElements(node); | ||
| 8036 | |||
| 8037 | return id; | ||
| 8038 | } | ||
| 8039 | |||
| 8040 | /// <summary> | ||
| 8041 | /// Parses a ReplacePatch element. | ||
| 8042 | /// </summary> | ||
| 8043 | /// <param name="node">The element to parse.</param> | ||
| 8044 | /// <returns>The id from the node.</returns> | ||
| 8045 | private string ParseReplacePatchElement(XElement node) | ||
| 8046 | { | ||
| 8047 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 8048 | string id = null; | ||
| 8049 | |||
| 8050 | foreach (var attrib in node.Attributes()) | ||
| 8051 | { | ||
| 8052 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 8053 | { | ||
| 8054 | switch (attrib.Name.LocalName) | ||
| 8055 | { | ||
| 8056 | case "Id": | ||
| 8057 | id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 8058 | break; | ||
| 8059 | default: | ||
| 8060 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 8061 | break; | ||
| 8062 | } | ||
| 8063 | } | ||
| 8064 | else | ||
| 8065 | { | ||
| 8066 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 8067 | } | ||
| 8068 | } | ||
| 8069 | |||
| 8070 | if (null == id) | ||
| 8071 | { | ||
| 8072 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 8073 | } | ||
| 8074 | |||
| 8075 | this.Core.ParseForExtensionElements(node); | ||
| 8076 | |||
| 8077 | return id; | ||
| 8078 | } | ||
| 8079 | |||
| 8080 | /// <summary> | ||
| 8081 | /// Parses a symbol path element. | ||
| 8082 | /// </summary> | ||
| 8083 | /// <param name="node">The element to parse.</param> | ||
| 8084 | /// <returns>The path from the node.</returns> | ||
| 8085 | private string ParseSymbolPathElement(XElement node) | ||
| 8086 | { | ||
| 8087 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 8088 | string path = null; | ||
| 8089 | |||
| 8090 | foreach (var attrib in node.Attributes()) | ||
| 8091 | { | ||
| 8092 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 8093 | { | ||
| 8094 | switch (attrib.Name.LocalName) | ||
| 8095 | { | ||
| 8096 | case "Path": | ||
| 8097 | path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 8098 | break; | ||
| 8099 | default: | ||
| 8100 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 8101 | break; | ||
| 8102 | } | ||
| 8103 | } | ||
| 8104 | else | ||
| 8105 | { | ||
| 8106 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 8107 | } | ||
| 8108 | } | ||
| 8109 | |||
| 8110 | if (null == path) | ||
| 8111 | { | ||
| 8112 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Path")); | ||
| 8113 | } | ||
| 8114 | |||
| 8115 | this.Core.ParseForExtensionElements(node); | ||
| 8116 | |||
| 8117 | return path; | ||
| 8118 | } | ||
| 8119 | |||
| 8120 | /// <summary> | ||
| 8121 | /// Parses the All element under a PatchFamily. | ||
| 8122 | /// </summary> | ||
| 8123 | /// <param name="node">The element to parse.</param> | ||
| 8124 | private void ParseAllElement(XElement node) | ||
| 8125 | { | ||
| 8126 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 8127 | |||
| 8128 | // find unexpected attributes | ||
| 8129 | foreach (var attrib in node.Attributes()) | ||
| 8130 | { | ||
| 8131 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 8132 | { | ||
| 8133 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 8134 | } | ||
| 8135 | else | ||
| 8136 | { | ||
| 8137 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 8138 | } | ||
| 8139 | } | ||
| 8140 | |||
| 8141 | this.Core.ParseForExtensionElements(node); | ||
| 8142 | |||
| 8143 | // Always warn when using the All element. | ||
| 8144 | this.Core.Write(WarningMessages.AllChangesIncludedInPatch(sourceLineNumbers)); | ||
| 8145 | |||
| 8146 | if (!this.Core.EncounteredError) | ||
| 8147 | { | ||
| 8148 | this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) | ||
| 8149 | { | ||
| 8150 | Table = "*", | ||
| 8151 | PrimaryKeys = "*", | ||
| 8152 | }); | ||
| 8153 | } | ||
| 8154 | } | ||
| 8155 | |||
| 8156 | /// <summary> | ||
| 8157 | /// Parses all reference elements under a PatchFamily. | ||
| 8158 | /// </summary> | ||
| 8159 | /// <param name="node">The element to parse.</param> | ||
| 8160 | /// <param name="tableName">Table that reference was made to.</param> | ||
| 8161 | private void ParsePatchChildRefElement(XElement node, string tableName) | ||
| 8162 | { | ||
| 8163 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 8164 | string id = null; | ||
| 8165 | |||
| 8166 | foreach (var attrib in node.Attributes()) | ||
| 8167 | { | ||
| 8168 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 8169 | { | ||
| 8170 | switch (attrib.Name.LocalName) | ||
| 8171 | { | ||
| 8172 | case "Id": | ||
| 8173 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 8174 | break; | ||
| 8175 | default: | ||
| 8176 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 8177 | break; | ||
| 8178 | } | ||
| 8179 | } | ||
| 8180 | else | ||
| 8181 | { | ||
| 8182 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 8183 | } | ||
| 8184 | } | ||
| 8185 | |||
| 8186 | if (null == id) | ||
| 8187 | { | ||
| 8188 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 8189 | } | ||
| 8190 | |||
| 8191 | this.Core.ParseForExtensionElements(node); | ||
| 8192 | |||
| 8193 | if (!this.Core.EncounteredError) | ||
| 8194 | { | ||
| 8195 | this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) | ||
| 8196 | { | ||
| 8197 | Table = tableName, | ||
| 8198 | PrimaryKeys = id | ||
| 8199 | }); | ||
| 8200 | } | ||
| 8201 | } | ||
| 8202 | |||
| 8203 | /// <summary> | ||
| 8204 | /// Parses a PatchBaseline element. | ||
| 8205 | /// </summary> | ||
| 8206 | /// <param name="node">The element to parse.</param> | ||
| 8207 | /// <param name="diskId">Media index from parent element.</param> | ||
| 8208 | private void ParsePatchBaselineElement(XElement node, int? diskId) | ||
| 8209 | { | ||
| 8210 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 8211 | Identifier id = null; | ||
| 8212 | var parsedValidate = false; | ||
| 8213 | var validationFlags = TransformFlags.PatchTransformDefault; | ||
| 8214 | string baselineFile = null; | ||
| 8215 | string updateFile = null; | ||
| 8216 | string transformFile = null; | ||
| 8217 | |||
| 8218 | foreach (var attrib in node.Attributes()) | ||
| 8219 | { | ||
| 8220 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 8221 | { | ||
| 8222 | switch (attrib.Name.LocalName) | ||
| 8223 | { | ||
| 8224 | case "Id": | ||
| 8225 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 8226 | break; | ||
| 8227 | case "DiskId": | ||
| 8228 | diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 8229 | break; | ||
| 8230 | case "BaselineFile": | ||
| 8231 | baselineFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 8232 | break; | ||
| 8233 | case "UpdateFile": | ||
| 8234 | updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 8235 | break; | ||
| 8236 | case "TransformFile": | ||
| 8237 | transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 8238 | break; | ||
| 8239 | default: | ||
| 8240 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 8241 | break; | ||
| 8242 | } | ||
| 8243 | } | ||
| 8244 | else | ||
| 8245 | { | ||
| 8246 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 8247 | } | ||
| 8248 | } | ||
| 8249 | |||
| 8250 | if (null == id) | ||
| 8251 | { | ||
| 8252 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 8253 | id = Identifier.Invalid; | ||
| 8254 | } | ||
| 8255 | else if (27 < id.Id.Length) | ||
| 8256 | { | ||
| 8257 | this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27)); | ||
| 8258 | } | ||
| 8259 | |||
| 8260 | if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile)) | ||
| 8261 | { | ||
| 8262 | if (String.IsNullOrEmpty(baselineFile)) | ||
| 8263 | { | ||
| 8264 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile")); | ||
| 8265 | } | ||
| 8266 | |||
| 8267 | if (String.IsNullOrEmpty(updateFile)) | ||
| 8268 | { | ||
| 8269 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile")); | ||
| 8270 | } | ||
| 8271 | |||
| 8272 | if (!String.IsNullOrEmpty(transformFile)) | ||
| 8273 | { | ||
| 8274 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile")); | ||
| 8275 | } | ||
| 8276 | } | ||
| 8277 | else if (String.IsNullOrEmpty(transformFile)) | ||
| 8278 | { | ||
| 8279 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true)); | ||
| 8280 | } | ||
| 8281 | |||
| 8282 | foreach (var child in node.Elements()) | ||
| 8283 | { | ||
| 8284 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 8285 | { | ||
| 8286 | switch (child.Name.LocalName) | ||
| 8287 | { | ||
| 8288 | case "Validate": | ||
| 8289 | if (parsedValidate) | ||
| 8290 | { | ||
| 8291 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 8292 | this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); | ||
| 8293 | } | ||
| 8294 | else | ||
| 8295 | { | ||
| 8296 | this.ParseValidateElement(child, ref validationFlags); | ||
| 8297 | parsedValidate = true; | ||
| 8298 | } | ||
| 8299 | break; | ||
| 8300 | default: | ||
| 8301 | this.Core.UnexpectedElement(node, child); | ||
| 8302 | break; | ||
| 8303 | } | ||
| 8304 | } | ||
| 8305 | else | ||
| 8306 | { | ||
| 8307 | this.Core.ParseExtensionElement(node, child); | ||
| 8308 | } | ||
| 8309 | } | ||
| 8310 | |||
| 8311 | if (!this.Core.EncounteredError) | ||
| 8312 | { | ||
| 8313 | this.Core.AddSymbol(new WixPatchBaselineSymbol(sourceLineNumbers, id) | ||
| 8314 | { | ||
| 8315 | DiskId = diskId ?? 1, | ||
| 8316 | ValidationFlags = validationFlags, | ||
| 8317 | BaselineFile = new IntermediateFieldPathValue { Path = baselineFile }, | ||
| 8318 | UpdateFile = new IntermediateFieldPathValue { Path = updateFile }, | ||
| 8319 | TransformFile = new IntermediateFieldPathValue { Path = transformFile }, | ||
| 8320 | }); | ||
| 8321 | } | ||
| 8322 | } | ||
| 8323 | |||
| 8324 | /// <summary> | ||
| 8325 | /// Parses a Validate element. | ||
| 8326 | /// </summary> | ||
| 8327 | /// <param name="node">The element to parse.</param> | ||
| 8328 | /// <param name="validationFlags">TransformValidation flags to use when creating the authoring patch transform.</param> | ||
| 8329 | private void ParseValidateElement(XElement node, ref TransformFlags validationFlags) | ||
| 8330 | { | ||
| 8331 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 8332 | |||
| 8333 | foreach (var attrib in node.Attributes()) | ||
| 8334 | { | ||
| 8335 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 8336 | { | ||
| 8337 | switch (attrib.Name.LocalName) | ||
| 8338 | { | ||
| 8339 | case "ProductId": | ||
| 8340 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 8341 | { | ||
| 8342 | validationFlags |= TransformFlags.ValidateProduct; | ||
| 8343 | } | ||
| 8344 | else | ||
| 8345 | { | ||
| 8346 | validationFlags &= ~TransformFlags.ValidateProduct; | ||
| 8347 | } | ||
| 8348 | break; | ||
| 8349 | case "ProductLanguage": | ||
| 8350 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 8351 | { | ||
| 8352 | validationFlags |= TransformFlags.ValidateLanguage; | ||
| 8353 | } | ||
| 8354 | else | ||
| 8355 | { | ||
| 8356 | validationFlags &= ~TransformFlags.ValidateLanguage; | ||
| 8357 | } | ||
| 8358 | break; | ||
| 8359 | case "ProductVersion": | ||
| 8360 | var check = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 8361 | validationFlags &= ~TransformFlags.ProductVersionMask; | ||
| 8362 | switch (check) | ||
| 8363 | { | ||
| 8364 | case "Major": | ||
| 8365 | case "major": | ||
| 8366 | validationFlags |= TransformFlags.ValidateMajorVersion; | ||
| 8367 | break; | ||
| 8368 | case "Minor": | ||
| 8369 | case "minor": | ||
| 8370 | validationFlags |= TransformFlags.ValidateMinorVersion; | ||
| 8371 | break; | ||
| 8372 | case "Update": | ||
| 8373 | case "update": | ||
| 8374 | validationFlags |= TransformFlags.ValidateUpdateVersion; | ||
| 8375 | break; | ||
| 8376 | case "": | ||
| 8377 | break; | ||
| 8378 | default: | ||
| 8379 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Version", check, "Major", "Minor", "Update")); | ||
| 8380 | break; | ||
| 8381 | } | ||
| 8382 | break; | ||
| 8383 | case "ProductVersionOperator": | ||
| 8384 | var op = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 8385 | validationFlags &= ~TransformFlags.ProductVersionOperatorMask; | ||
| 8386 | switch (op) | ||
| 8387 | { | ||
| 8388 | case "Lesser": | ||
| 8389 | case "lesser": | ||
| 8390 | validationFlags |= TransformFlags.ValidateNewLessBaseVersion; | ||
| 8391 | break; | ||
| 8392 | case "LesserOrEqual": | ||
| 8393 | case "lesserOrEqual": | ||
| 8394 | validationFlags |= TransformFlags.ValidateNewLessEqualBaseVersion; | ||
| 8395 | break; | ||
| 8396 | case "Equal": | ||
| 8397 | case "equal": | ||
| 8398 | validationFlags |= TransformFlags.ValidateNewEqualBaseVersion; | ||
| 8399 | break; | ||
| 8400 | case "GreaterOrEqual": | ||
| 8401 | case "greaterOrEqual": | ||
| 8402 | validationFlags |= TransformFlags.ValidateNewGreaterEqualBaseVersion; | ||
| 8403 | break; | ||
| 8404 | case "Greater": | ||
| 8405 | case "greater": | ||
| 8406 | validationFlags |= TransformFlags.ValidateNewGreaterBaseVersion; | ||
| 8407 | break; | ||
| 8408 | case "": | ||
| 8409 | break; | ||
| 8410 | default: | ||
| 8411 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Operator", op, "Lesser", "LesserOrEqual", "Equal", "GreaterOrEqual", "Greater")); | ||
| 8412 | break; | ||
| 8413 | } | ||
| 8414 | break; | ||
| 8415 | case "UpgradeCode": | ||
| 8416 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 8417 | { | ||
| 8418 | validationFlags |= TransformFlags.ValidateUpgradeCode; | ||
| 8419 | } | ||
| 8420 | else | ||
| 8421 | { | ||
| 8422 | validationFlags &= ~TransformFlags.ValidateUpgradeCode; | ||
| 8423 | } | ||
| 8424 | break; | ||
| 8425 | case "IgnoreAddExistingRow": | ||
| 8426 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 8427 | { | ||
| 8428 | validationFlags |= TransformFlags.ErrorAddExistingRow; | ||
| 8429 | } | ||
| 8430 | else | ||
| 8431 | { | ||
| 8432 | validationFlags &= ~TransformFlags.ErrorAddExistingRow; | ||
| 8433 | } | ||
| 8434 | break; | ||
| 8435 | case "IgnoreAddExistingTable": | ||
| 8436 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 8437 | { | ||
| 8438 | validationFlags |= TransformFlags.ErrorAddExistingTable; | ||
| 8439 | } | ||
| 8440 | else | ||
| 8441 | { | ||
| 8442 | validationFlags &= ~TransformFlags.ErrorAddExistingTable; | ||
| 8443 | } | ||
| 8444 | break; | ||
| 8445 | case "IgnoreDeleteMissingRow": | ||
| 8446 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 8447 | { | ||
| 8448 | validationFlags |= TransformFlags.ErrorDeleteMissingRow; | ||
| 8449 | } | ||
| 8450 | else | ||
| 8451 | { | ||
| 8452 | validationFlags &= ~TransformFlags.ErrorDeleteMissingRow; | ||
| 8453 | } | ||
| 8454 | break; | ||
| 8455 | case "IgnoreDeleteMissingTable": | ||
| 8456 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 8457 | { | ||
| 8458 | validationFlags |= TransformFlags.ErrorDeleteMissingTable; | ||
| 8459 | } | ||
| 8460 | else | ||
| 8461 | { | ||
| 8462 | validationFlags &= ~TransformFlags.ErrorDeleteMissingTable; | ||
| 8463 | } | ||
| 8464 | break; | ||
| 8465 | case "IgnoreUpdateMissingRow": | ||
| 8466 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 8467 | { | ||
| 8468 | validationFlags |= TransformFlags.ErrorUpdateMissingRow; | ||
| 8469 | } | ||
| 8470 | else | ||
| 8471 | { | ||
| 8472 | validationFlags &= ~TransformFlags.ErrorUpdateMissingRow; | ||
| 8473 | } | ||
| 8474 | break; | ||
| 8475 | case "IgnoreChangingCodePage": | ||
| 8476 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 8477 | { | ||
| 8478 | validationFlags |= TransformFlags.ErrorChangeCodePage; | ||
| 8479 | } | ||
| 8480 | else | ||
| 8481 | { | ||
| 8482 | validationFlags &= ~TransformFlags.ErrorChangeCodePage; | ||
| 8483 | } | ||
| 8484 | break; | ||
| 8485 | default: | ||
| 8486 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 8487 | break; | ||
| 8488 | } | ||
| 8489 | } | ||
| 8490 | else | ||
| 8491 | { | ||
| 8492 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 8493 | } | ||
| 8494 | } | ||
| 8495 | } | ||
| 8496 | |||
| 8497 | private string HandleSubdirectory(SourceLineNumber sourceLineNumbers, XElement element, string directoryId, string subdirectory, string directoryAttributeName, string subdirectoryAttributename) | ||
| 8498 | { | ||
| 8499 | if (!String.IsNullOrEmpty(subdirectory)) | ||
| 8500 | { | ||
| 8501 | if (String.IsNullOrEmpty(directoryId)) | ||
| 8502 | { | ||
| 8503 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, element.Name.LocalName, subdirectoryAttributename, directoryAttributeName)); | ||
| 8504 | } | ||
| 8505 | else | ||
| 8506 | { | ||
| 8507 | directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); | ||
| 8508 | } | ||
| 8509 | } | ||
| 8510 | |||
| 8511 | return directoryId; | ||
| 8512 | } | ||
| 8513 | } | ||
| 8514 | } | ||
diff --git a/src/wix/WixToolset.Core/CompilerCore.cs b/src/wix/WixToolset.Core/CompilerCore.cs new file mode 100644 index 00000000..727084eb --- /dev/null +++ b/src/wix/WixToolset.Core/CompilerCore.cs | |||
| @@ -0,0 +1,1166 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Diagnostics; | ||
| 9 | using System.Globalization; | ||
| 10 | using System.Reflection; | ||
| 11 | using System.Text; | ||
| 12 | using System.Xml.Linq; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Symbols; | ||
| 15 | using WixToolset.Data.WindowsInstaller; | ||
| 16 | using WixToolset.Extensibility; | ||
| 17 | using WixToolset.Extensibility.Data; | ||
| 18 | using WixToolset.Extensibility.Services; | ||
| 19 | |||
| 20 | internal enum ValueListKind | ||
| 21 | { | ||
| 22 | /// <summary> | ||
| 23 | /// A list of values with nothing before the final value. | ||
| 24 | /// </summary> | ||
| 25 | None, | ||
| 26 | |||
| 27 | /// <summary> | ||
| 28 | /// A list of values with 'and' before the final value. | ||
| 29 | /// </summary> | ||
| 30 | And, | ||
| 31 | |||
| 32 | /// <summary> | ||
| 33 | /// A list of values with 'or' before the final value. | ||
| 34 | /// </summary> | ||
| 35 | Or | ||
| 36 | } | ||
| 37 | |||
| 38 | /// <summary> | ||
| 39 | /// Core class for the compiler. | ||
| 40 | /// </summary> | ||
| 41 | internal class CompilerCore | ||
| 42 | { | ||
| 43 | internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; | ||
| 44 | internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; | ||
| 45 | |||
| 46 | // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) | ||
| 47 | private static readonly List<string> BuiltinBundleVariables = new List<string>( | ||
| 48 | new string[] { | ||
| 49 | "AdminToolsFolder", | ||
| 50 | "AppDataFolder", | ||
| 51 | "CommonAppDataFolder", | ||
| 52 | "CommonFiles64Folder", | ||
| 53 | "CommonFilesFolder", | ||
| 54 | "CompatibilityMode", | ||
| 55 | "Date", | ||
| 56 | "DesktopFolder", | ||
| 57 | "FavoritesFolder", | ||
| 58 | "FontsFolder", | ||
| 59 | "InstallerName", | ||
| 60 | "InstallerVersion", | ||
| 61 | "LocalAppDataFolder", | ||
| 62 | "LogonUser", | ||
| 63 | "MyPicturesFolder", | ||
| 64 | "NTProductType", | ||
| 65 | "NTSuiteBackOffice", | ||
| 66 | "NTSuiteDataCenter", | ||
| 67 | "NTSuiteEnterprise", | ||
| 68 | "NTSuitePersonal", | ||
| 69 | "NTSuiteSmallBusiness", | ||
| 70 | "NTSuiteSmallBusinessRestricted", | ||
| 71 | "NTSuiteWebServer", | ||
| 72 | "PersonalFolder", | ||
| 73 | "Privileged", | ||
| 74 | "ProgramFiles64Folder", | ||
| 75 | "ProgramFiles6432Folder", | ||
| 76 | "ProgramFilesFolder", | ||
| 77 | "ProgramMenuFolder", | ||
| 78 | "RebootPending", | ||
| 79 | "SendToFolder", | ||
| 80 | "ServicePackLevel", | ||
| 81 | "StartMenuFolder", | ||
| 82 | "StartupFolder", | ||
| 83 | "System64Folder", | ||
| 84 | "SystemFolder", | ||
| 85 | "TempFolder", | ||
| 86 | "TemplateFolder", | ||
| 87 | "TerminalServer", | ||
| 88 | "UserLanguageID", | ||
| 89 | "UserUILanguageID", | ||
| 90 | "VersionMsi", | ||
| 91 | "VersionNT", | ||
| 92 | "VersionNT64", | ||
| 93 | "WindowsFolder", | ||
| 94 | "WindowsVolume", | ||
| 95 | "WixBundleAction", | ||
| 96 | "WixBundleForcedRestartPackage", | ||
| 97 | "WixBundleElevated", | ||
| 98 | "WixBundleInstalled", | ||
| 99 | "WixBundleProviderKey", | ||
| 100 | "WixBundleTag", | ||
| 101 | "WixBundleVersion", | ||
| 102 | }); | ||
| 103 | |||
| 104 | private static readonly List<string> DisallowedMsiProperties = new List<string>( | ||
| 105 | new string[] { | ||
| 106 | "ACTION", | ||
| 107 | "ADDLOCAL", | ||
| 108 | "ADDSOURCE", | ||
| 109 | "ADDDEFAULT", | ||
| 110 | "ADVERTISE", | ||
| 111 | "ALLUSERS", | ||
| 112 | "REBOOT", | ||
| 113 | "REINSTALL", | ||
| 114 | "REINSTALLMODE", | ||
| 115 | "REMOVE" | ||
| 116 | }); | ||
| 117 | |||
| 118 | private readonly Dictionary<XNamespace, ICompilerExtension> extensions; | ||
| 119 | private readonly IParseHelper parseHelper; | ||
| 120 | private readonly Intermediate intermediate; | ||
| 121 | private readonly IMessaging messaging; | ||
| 122 | private Dictionary<string, string> activeSectionCachedInlinedDirectoryIds; | ||
| 123 | private HashSet<string> activeSectionSimpleReferences; | ||
| 124 | |||
| 125 | /// <summary> | ||
| 126 | /// Constructor for all compiler core. | ||
| 127 | /// </summary> | ||
| 128 | /// <param name="intermediate">The Intermediate object representing compiled source document.</param> | ||
| 129 | /// <param name="messaging"></param> | ||
| 130 | /// <param name="parseHelper"></param> | ||
| 131 | /// <param name="extensions">The WiX extensions collection.</param> | ||
| 132 | internal CompilerCore(Intermediate intermediate, IMessaging messaging, IParseHelper parseHelper, Dictionary<XNamespace, ICompilerExtension> extensions) | ||
| 133 | { | ||
| 134 | this.extensions = extensions; | ||
| 135 | this.parseHelper = parseHelper; | ||
| 136 | this.intermediate = intermediate; | ||
| 137 | this.messaging = messaging; | ||
| 138 | } | ||
| 139 | |||
| 140 | /// <summary> | ||
| 141 | /// Gets the section the compiler is currently emitting symbols into. | ||
| 142 | /// </summary> | ||
| 143 | /// <value>The section the compiler is currently emitting symbols into.</value> | ||
| 144 | public IntermediateSection ActiveSection { get; private set; } | ||
| 145 | |||
| 146 | /// <summary> | ||
| 147 | /// Gets whether the compiler core encountered an error while processing. | ||
| 148 | /// </summary> | ||
| 149 | /// <value>Flag if core encountered an error during processing.</value> | ||
| 150 | public bool EncounteredError => this.messaging.EncounteredError; | ||
| 151 | |||
| 152 | /// <summary> | ||
| 153 | /// Gets or sets the option to show pedantic messages. | ||
| 154 | /// </summary> | ||
| 155 | /// <value>The option to show pedantic messages.</value> | ||
| 156 | public bool ShowPedanticMessages { get; set; } | ||
| 157 | |||
| 158 | /// <summary> | ||
| 159 | /// Add a symbol to the active section. | ||
| 160 | /// </summary> | ||
| 161 | /// <param name="symbol">Symbol to add.</param> | ||
| 162 | public T AddSymbol<T>(T symbol) | ||
| 163 | where T : IntermediateSymbol | ||
| 164 | { | ||
| 165 | return this.ActiveSection.AddSymbol(symbol); | ||
| 166 | } | ||
| 167 | |||
| 168 | /// <summary> | ||
| 169 | /// Convert a bit array into an int value. | ||
| 170 | /// </summary> | ||
| 171 | /// <param name="bits">The bit array to convert.</param> | ||
| 172 | /// <returns>The converted int value.</returns> | ||
| 173 | public int CreateIntegerFromBitArray(BitArray bits) | ||
| 174 | { | ||
| 175 | if (32 != bits.Length) | ||
| 176 | { | ||
| 177 | throw new ArgumentException(String.Format("Can only convert a bit array with 32-bits to integer. Actual number of bits in array: {0}", bits.Length), "bits"); | ||
| 178 | } | ||
| 179 | |||
| 180 | int[] intArray = new int[1]; | ||
| 181 | bits.CopyTo(intArray, 0); | ||
| 182 | |||
| 183 | return intArray[0]; | ||
| 184 | } | ||
| 185 | |||
| 186 | /// <summary> | ||
| 187 | /// Sets a bit in a bit array based on the index at which an attribute name was found in a string array. | ||
| 188 | /// </summary> | ||
| 189 | /// <param name="attributeNames">Array of attributes that map to bits.</param> | ||
| 190 | /// <param name="attributeName">Name of attribute to check.</param> | ||
| 191 | /// <param name="attributeValue">Value of attribute to check.</param> | ||
| 192 | /// <param name="bits">The bit array in which the bit will be set if found.</param> | ||
| 193 | /// <param name="offset">The offset into the bit array.</param> | ||
| 194 | /// <returns>true if the bit was set; false otherwise.</returns> | ||
| 195 | public bool TrySetBitFromName(string[] attributeNames, string attributeName, YesNoType attributeValue, BitArray bits, int offset) | ||
| 196 | { | ||
| 197 | for (int i = 0; i < attributeNames.Length; i++) | ||
| 198 | { | ||
| 199 | if (attributeName.Equals(attributeNames[i], StringComparison.Ordinal)) | ||
| 200 | { | ||
| 201 | bits.Set(i + offset, YesNoType.Yes == attributeValue); | ||
| 202 | return true; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | return false; | ||
| 207 | } | ||
| 208 | |||
| 209 | internal void InnerTextDisallowed(XElement element) | ||
| 210 | { | ||
| 211 | this.parseHelper.InnerTextDisallowed(element); | ||
| 212 | } | ||
| 213 | |||
| 214 | /// <summary> | ||
| 215 | /// Verifies that a filename is ambiguous. | ||
| 216 | /// </summary> | ||
| 217 | /// <param name="filename">Filename to verify.</param> | ||
| 218 | /// <returns>true if the filename is ambiguous; false otherwise.</returns> | ||
| 219 | public static bool IsAmbiguousFilename(string filename) | ||
| 220 | { | ||
| 221 | if (String.IsNullOrEmpty(filename)) | ||
| 222 | { | ||
| 223 | return false; | ||
| 224 | } | ||
| 225 | |||
| 226 | var tilde = filename.IndexOf('~'); | ||
| 227 | return (tilde > 0 && tilde < filename.Length) && Char.IsNumber(filename[tilde + 1]); | ||
| 228 | } | ||
| 229 | |||
| 230 | /// <summary> | ||
| 231 | /// Verifies that a value is a legal identifier. | ||
| 232 | /// </summary> | ||
| 233 | /// <param name="value">The value to verify.</param> | ||
| 234 | /// <returns>true if the value is an identifier; false otherwise.</returns> | ||
| 235 | public bool IsValidIdentifier(string value) | ||
| 236 | { | ||
| 237 | return this.parseHelper.IsValidIdentifier(value); | ||
| 238 | } | ||
| 239 | |||
| 240 | /// <summary> | ||
| 241 | /// Verifies if an identifier is a valid loc identifier. | ||
| 242 | /// </summary> | ||
| 243 | /// <param name="identifier">Identifier to verify.</param> | ||
| 244 | /// <returns>True if the identifier is a valid loc identifier.</returns> | ||
| 245 | public bool IsValidLocIdentifier(string identifier) | ||
| 246 | { | ||
| 247 | return this.parseHelper.IsValidLocIdentifier(identifier); | ||
| 248 | } | ||
| 249 | |||
| 250 | /// <summary> | ||
| 251 | /// Verifies if a filename is a valid long filename. | ||
| 252 | /// </summary> | ||
| 253 | /// <param name="filename">Filename to verify.</param> | ||
| 254 | /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param> | ||
| 255 | /// <param name="allowRelative">true if relative paths are allowed in the filename.</param> | ||
| 256 | /// <returns>True if the filename is a valid long filename</returns> | ||
| 257 | public bool IsValidLongFilename(string filename, bool allowWildcards = false, bool allowRelative = false) | ||
| 258 | { | ||
| 259 | return this.parseHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); | ||
| 260 | } | ||
| 261 | |||
| 262 | /// <summary> | ||
| 263 | /// Verifies if a filename is a valid short filename. | ||
| 264 | /// </summary> | ||
| 265 | /// <param name="filename">Filename to verify.</param> | ||
| 266 | /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param> | ||
| 267 | /// <returns>True if the filename is a valid short filename</returns> | ||
| 268 | public bool IsValidShortFilename(string filename, bool allowWildcards) | ||
| 269 | { | ||
| 270 | return this.parseHelper.IsValidShortFilename(filename, allowWildcards); | ||
| 271 | } | ||
| 272 | |||
| 273 | /// <summary> | ||
| 274 | /// Replaces the illegal filename characters to create a legal name. | ||
| 275 | /// </summary> | ||
| 276 | /// <param name="filename">Filename to make valid.</param> | ||
| 277 | /// <param name="replace">Replacement string for invalid characters in filename.</param> | ||
| 278 | /// <returns>Valid filename.</returns> | ||
| 279 | public static string MakeValidLongFileName(string filename, char replace) | ||
| 280 | { | ||
| 281 | if (String.IsNullOrEmpty(filename)) | ||
| 282 | { | ||
| 283 | return filename; | ||
| 284 | } | ||
| 285 | |||
| 286 | StringBuilder sb = null; | ||
| 287 | |||
| 288 | var found = filename.IndexOfAny(Common.IllegalLongFilenameCharacters); | ||
| 289 | while (found != -1) | ||
| 290 | { | ||
| 291 | if (sb == null) | ||
| 292 | { | ||
| 293 | sb = new StringBuilder(filename); | ||
| 294 | } | ||
| 295 | |||
| 296 | sb[found] = replace; | ||
| 297 | |||
| 298 | found = (found + 1 < filename.Length) ? filename.IndexOfAny(Common.IllegalLongFilenameCharacters, found + 1) : -1; | ||
| 299 | } | ||
| 300 | |||
| 301 | return sb?.ToString() ?? filename; | ||
| 302 | } | ||
| 303 | |||
| 304 | /// <summary> | ||
| 305 | /// Verifies the given string is a valid product version. | ||
| 306 | /// </summary> | ||
| 307 | /// <param name="version">The product version to verify.</param> | ||
| 308 | /// <returns>True if version is a valid product version</returns> | ||
| 309 | public static bool IsValidProductVersion(string version) | ||
| 310 | { | ||
| 311 | if (!Common.IsValidBinderVariable(version)) | ||
| 312 | { | ||
| 313 | Version ver = new Version(version); | ||
| 314 | |||
| 315 | if (255 < ver.Major || 255 < ver.Minor || 65535 < ver.Build) | ||
| 316 | { | ||
| 317 | return false; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | return true; | ||
| 322 | } | ||
| 323 | |||
| 324 | /// <summary> | ||
| 325 | /// Verifies the given string is a valid module or bundle version. | ||
| 326 | /// </summary> | ||
| 327 | /// <param name="version">The version to verify.</param> | ||
| 328 | /// <returns>True if version is a valid module or bundle version.</returns> | ||
| 329 | public static bool IsValidModuleOrBundleVersion(string version) | ||
| 330 | { | ||
| 331 | return Common.IsValidFourPartVersion(version); | ||
| 332 | } | ||
| 333 | |||
| 334 | /// <summary> | ||
| 335 | /// Creates group and ordering information. | ||
| 336 | /// </summary> | ||
| 337 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
| 338 | /// <param name="parentType">Type of parent group, if known.</param> | ||
| 339 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
| 340 | /// <param name="type">Type of this item.</param> | ||
| 341 | /// <param name="id">Identifier for this item.</param> | ||
| 342 | /// <param name="previousType">Type of previous item, if known.</param> | ||
| 343 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
| 344 | public void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers, | ||
| 345 | ComplexReferenceParentType parentType, string parentId, | ||
| 346 | ComplexReferenceChildType type, string id, | ||
| 347 | ComplexReferenceChildType previousType, string previousId) | ||
| 348 | { | ||
| 349 | if (this.EncounteredError) | ||
| 350 | { | ||
| 351 | return; | ||
| 352 | } | ||
| 353 | |||
| 354 | if (parentType != ComplexReferenceParentType.Unknown && parentId != null) | ||
| 355 | { | ||
| 356 | this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id); | ||
| 357 | } | ||
| 358 | |||
| 359 | if (previousType != ComplexReferenceChildType.Unknown && previousId != null) | ||
| 360 | { | ||
| 361 | // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"? | ||
| 362 | // TODO: Also, we could potentially include an 'Attributes' field to track things like | ||
| 363 | // 'before' vs. 'after', and explicit vs. inferred dependencies. | ||
| 364 | this.AddSymbol(new WixOrderingSymbol(sourceLineNumbers) | ||
| 365 | { | ||
| 366 | ItemType = type, | ||
| 367 | ItemIdRef = id, | ||
| 368 | DependsOnType = previousType, | ||
| 369 | DependsOnIdRef = previousId, | ||
| 370 | }); | ||
| 371 | } | ||
| 372 | } | ||
| 373 | |||
| 374 | /// <summary> | ||
| 375 | /// Creates a version 3 name-based UUID. | ||
| 376 | /// </summary> | ||
| 377 | /// <param name="namespaceGuid">The namespace UUID.</param> | ||
| 378 | /// <param name="value">The value.</param> | ||
| 379 | /// <returns>The generated GUID for the given namespace and value.</returns> | ||
| 380 | public string CreateGuid(Guid namespaceGuid, string value) | ||
| 381 | { | ||
| 382 | return this.parseHelper.CreateGuid(namespaceGuid, value); | ||
| 383 | } | ||
| 384 | |||
| 385 | /// <summary> | ||
| 386 | /// Creates directories using the inline directory syntax. | ||
| 387 | /// </summary> | ||
| 388 | /// <param name="sourceLineNumbers">Source line information.</param> | ||
| 389 | /// <param name="parentId">Optional identifier of parent directory.</param> | ||
| 390 | /// <param name="inlineSyntax">Optional inline syntax to override attribute's value.</param> | ||
| 391 | /// <returns>Identifier of the leaf directory created.</returns> | ||
| 392 | public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, string parentId, string inlineSyntax = null) | ||
| 393 | { | ||
| 394 | return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute: null, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds); | ||
| 395 | } | ||
| 396 | |||
| 397 | /// <summary> | ||
| 398 | /// Creates a Registry row in the active section. | ||
| 399 | /// </summary> | ||
| 400 | /// <param name="sourceLineNumbers">Source and line number of the current row.</param> | ||
| 401 | /// <param name="root">The registry entry root.</param> | ||
| 402 | /// <param name="key">The registry entry key.</param> | ||
| 403 | /// <param name="name">The registry entry name.</param> | ||
| 404 | /// <param name="value">The registry entry value.</param> | ||
| 405 | /// <param name="componentId">The component which will control installation/uninstallation of the registry entry.</param> | ||
| 406 | public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId) | ||
| 407 | { | ||
| 408 | return this.parseHelper.CreateRegistrySymbol(this.ActiveSection, sourceLineNumbers, root, key, name, value, componentId, true); | ||
| 409 | } | ||
| 410 | |||
| 411 | /// <summary> | ||
| 412 | /// Create a WixSimpleReferenceSymbol in the active section. | ||
| 413 | /// </summary> | ||
| 414 | /// <param name="sourceLineNumbers">Source line information for the row.</param> | ||
| 415 | /// <param name="symbolName">The symbol name of the simple reference.</param> | ||
| 416 | /// <param name="primaryKey">The primary key of the simple reference.</param> | ||
| 417 | public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) | ||
| 418 | { | ||
| 419 | if (!this.EncounteredError) | ||
| 420 | { | ||
| 421 | var id = String.Concat(symbolName, ":", primaryKey); | ||
| 422 | |||
| 423 | // If this simple reference hasn't been added to the active section already, add it. | ||
| 424 | if (this.activeSectionSimpleReferences.Add(id)) | ||
| 425 | { | ||
| 426 | this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKey); | ||
| 427 | } | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | /// <summary> | ||
| 432 | /// Create a WixSimpleReferenceSymbol in the active section. | ||
| 433 | /// </summary> | ||
| 434 | /// <param name="sourceLineNumbers">Source line information for the row.</param> | ||
| 435 | /// <param name="symbolName">The symbol name of the simple reference.</param> | ||
| 436 | /// <param name="primaryKeys">The primary keys of the simple reference.</param> | ||
| 437 | public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) | ||
| 438 | { | ||
| 439 | if (!this.EncounteredError) | ||
| 440 | { | ||
| 441 | var joinedKeys = String.Join("/", primaryKeys); | ||
| 442 | var id = String.Concat(symbolName, ":", joinedKeys); | ||
| 443 | |||
| 444 | // If this simple reference hasn't been added to the active section already, add it. | ||
| 445 | if (this.activeSectionSimpleReferences.Add(id)) | ||
| 446 | { | ||
| 447 | this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKeys); | ||
| 448 | } | ||
| 449 | } | ||
| 450 | } | ||
| 451 | |||
| 452 | /// <summary> | ||
| 453 | /// Create a WixSimpleReferenceSymbol in the active section. | ||
| 454 | /// </summary> | ||
| 455 | /// <param name="sourceLineNumbers">Source line information for the row.</param> | ||
| 456 | /// <param name="symbolDefinition">The symbol definition of the simple reference.</param> | ||
| 457 | /// <param name="primaryKey">The primary key of the simple reference.</param> | ||
| 458 | public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) | ||
| 459 | { | ||
| 460 | this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKey); | ||
| 461 | } | ||
| 462 | |||
| 463 | /// <summary> | ||
| 464 | /// Create a WixSimpleReferenceSymbol in the active section. | ||
| 465 | /// </summary> | ||
| 466 | /// <param name="sourceLineNumbers">Source line information for the row.</param> | ||
| 467 | /// <param name="symbolDefinition">The symbol definition of the simple reference.</param> | ||
| 468 | /// <param name="primaryKeys">The primary keys of the simple reference.</param> | ||
| 469 | public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) | ||
| 470 | { | ||
| 471 | this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKeys); | ||
| 472 | } | ||
| 473 | |||
| 474 | /// <summary> | ||
| 475 | /// A row in the WixGroup table is added for this child node and its parent node. | ||
| 476 | /// </summary> | ||
| 477 | /// <param name="sourceLineNumbers">Source line information for the row.</param> | ||
| 478 | /// <param name="parentType">Type of child's complex reference parent.</param> | ||
| 479 | /// <param name="parentId">Id of the parenet node.</param> | ||
| 480 | /// <param name="childType">Complex reference type of child</param> | ||
| 481 | /// <param name="childId">Id of the Child Node.</param> | ||
| 482 | public void CreateWixGroupRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) | ||
| 483 | { | ||
| 484 | if (!this.EncounteredError) | ||
| 485 | { | ||
| 486 | this.parseHelper.CreateWixGroupSymbol(this.ActiveSection, sourceLineNumbers, parentType, parentId, childType, childId); | ||
| 487 | } | ||
| 488 | } | ||
| 489 | |||
| 490 | /// <summary> | ||
| 491 | /// Add the appropriate symbols to make sure that the given table shows up | ||
| 492 | /// in the resulting output. | ||
| 493 | /// </summary> | ||
| 494 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
| 495 | /// <param name="tableName">Name of the table to ensure existance of.</param> | ||
| 496 | public void EnsureTable(SourceLineNumber sourceLineNumbers, string tableName) | ||
| 497 | { | ||
| 498 | if (!this.EncounteredError) | ||
| 499 | { | ||
| 500 | this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableName); | ||
| 501 | } | ||
| 502 | } | ||
| 503 | |||
| 504 | /// <summary> | ||
| 505 | /// Add the appropriate symbols to make sure that the given table shows up | ||
| 506 | /// in the resulting output. | ||
| 507 | /// </summary> | ||
| 508 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
| 509 | /// <param name="tableDefinition">Definition of the table to ensure existance of.</param> | ||
| 510 | public void EnsureTable(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) | ||
| 511 | { | ||
| 512 | if (!this.EncounteredError) | ||
| 513 | { | ||
| 514 | this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableDefinition); | ||
| 515 | } | ||
| 516 | } | ||
| 517 | |||
| 518 | /// <summary> | ||
| 519 | /// Get an attribute value. | ||
| 520 | /// </summary> | ||
| 521 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 522 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 523 | /// <param name="emptyRule">A rule for the contents of the value. If the contents do not follow the rule, an error is thrown.</param> | ||
| 524 | /// <returns>The attribute's value.</returns> | ||
| 525 | public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) | ||
| 526 | { | ||
| 527 | return this.parseHelper.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); | ||
| 528 | } | ||
| 529 | |||
| 530 | /// <summary> | ||
| 531 | /// Get a valid code page by web name or number from a string attribute. | ||
| 532 | /// </summary> | ||
| 533 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 534 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 535 | /// <returns>A valid code page integer value.</returns> | ||
| 536 | public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 537 | { | ||
| 538 | if (null == attribute) | ||
| 539 | { | ||
| 540 | throw new ArgumentNullException(nameof(attribute)); | ||
| 541 | } | ||
| 542 | |||
| 543 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 544 | |||
| 545 | try | ||
| 546 | { | ||
| 547 | return Common.GetValidCodePage(value); | ||
| 548 | } | ||
| 549 | catch (NotSupportedException) | ||
| 550 | { | ||
| 551 | this.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); | ||
| 552 | } | ||
| 553 | |||
| 554 | return CompilerConstants.IllegalInteger; | ||
| 555 | } | ||
| 556 | |||
| 557 | /// <summary> | ||
| 558 | /// Get a valid code page by web name or number from a string attribute. | ||
| 559 | /// </summary> | ||
| 560 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 561 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 562 | /// <param name="onlyAnsi">Whether to allow Unicode (UCS) or UTF code pages.</param> | ||
| 563 | /// <returns>A valid code page integer value or variable expression.</returns> | ||
| 564 | public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false) | ||
| 565 | { | ||
| 566 | if (null == attribute) | ||
| 567 | { | ||
| 568 | throw new ArgumentNullException(nameof(attribute)); | ||
| 569 | } | ||
| 570 | |||
| 571 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 572 | |||
| 573 | // Allow for localization of code page names and values. | ||
| 574 | if (this.IsValidLocIdentifier(value)) | ||
| 575 | { | ||
| 576 | return value; | ||
| 577 | } | ||
| 578 | |||
| 579 | try | ||
| 580 | { | ||
| 581 | var codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers); | ||
| 582 | return codePage.ToString(CultureInfo.InvariantCulture); | ||
| 583 | } | ||
| 584 | catch (NotSupportedException) | ||
| 585 | { | ||
| 586 | // Not a valid windows code page. | ||
| 587 | this.messaging.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); | ||
| 588 | } | ||
| 589 | catch (WixException e) | ||
| 590 | { | ||
| 591 | this.messaging.Write(e.Error); | ||
| 592 | } | ||
| 593 | |||
| 594 | return null; | ||
| 595 | } | ||
| 596 | |||
| 597 | /// <summary> | ||
| 598 | /// Get an integer attribute value and displays an error for an illegal integer value. | ||
| 599 | /// </summary> | ||
| 600 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 601 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 602 | /// <param name="minimum">The minimum legal value.</param> | ||
| 603 | /// <param name="maximum">The maximum legal value.</param> | ||
| 604 | /// <returns>The attribute's integer value or a special value if an error occurred during conversion.</returns> | ||
| 605 | public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) | ||
| 606 | { | ||
| 607 | return this.parseHelper.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum); | ||
| 608 | } | ||
| 609 | |||
| 610 | /// <summary> | ||
| 611 | /// Get a long integral attribute value and displays an error for an illegal long value. | ||
| 612 | /// </summary> | ||
| 613 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 614 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 615 | /// <param name="minimum">The minimum legal value.</param> | ||
| 616 | /// <param name="maximum">The maximum legal value.</param> | ||
| 617 | /// <returns>The attribute's long value or a special value if an error occurred during conversion.</returns> | ||
| 618 | public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) | ||
| 619 | { | ||
| 620 | return this.parseHelper.GetAttributeLongValue(sourceLineNumbers, attribute, minimum, maximum); | ||
| 621 | } | ||
| 622 | |||
| 623 | /// <summary> | ||
| 624 | /// Get a date time attribute value and display errors for illegal values. | ||
| 625 | /// </summary> | ||
| 626 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 627 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 628 | /// <returns>Int representation of the date time.</returns> | ||
| 629 | public int GetAttributeDateTimeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 630 | { | ||
| 631 | if (null == attribute) | ||
| 632 | { | ||
| 633 | throw new ArgumentNullException("attribute"); | ||
| 634 | } | ||
| 635 | |||
| 636 | string value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 637 | |||
| 638 | if (0 < value.Length) | ||
| 639 | { | ||
| 640 | try | ||
| 641 | { | ||
| 642 | DateTime date = DateTime.Parse(value, CultureInfo.InvariantCulture.DateTimeFormat); | ||
| 643 | |||
| 644 | return ((((date.Year - 1980) * 512) + (date.Month * 32 + date.Day)) * 65536) + | ||
| 645 | (date.Hour * 2048) + (date.Minute * 32) + (date.Second / 2); | ||
| 646 | } | ||
| 647 | catch (ArgumentOutOfRangeException) | ||
| 648 | { | ||
| 649 | this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 650 | } | ||
| 651 | catch (FormatException) | ||
| 652 | { | ||
| 653 | this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 654 | } | ||
| 655 | catch (OverflowException) | ||
| 656 | { | ||
| 657 | this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 658 | } | ||
| 659 | } | ||
| 660 | |||
| 661 | return CompilerConstants.IllegalInteger; | ||
| 662 | } | ||
| 663 | |||
| 664 | /// <summary> | ||
| 665 | /// Get an integer attribute value or localize variable and displays an error for | ||
| 666 | /// an illegal value. | ||
| 667 | /// </summary> | ||
| 668 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 669 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 670 | /// <param name="minimum">The minimum legal value.</param> | ||
| 671 | /// <param name="maximum">The maximum legal value.</param> | ||
| 672 | /// <returns>The attribute's integer value or localize variable as a string or a special value if an error occurred during conversion.</returns> | ||
| 673 | public string GetAttributeLocalizableIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) | ||
| 674 | { | ||
| 675 | if (null == attribute) | ||
| 676 | { | ||
| 677 | throw new ArgumentNullException("attribute"); | ||
| 678 | } | ||
| 679 | |||
| 680 | Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); | ||
| 681 | |||
| 682 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 683 | |||
| 684 | if (0 < value.Length) | ||
| 685 | { | ||
| 686 | if (this.IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value)) | ||
| 687 | { | ||
| 688 | return value; | ||
| 689 | } | ||
| 690 | else | ||
| 691 | { | ||
| 692 | try | ||
| 693 | { | ||
| 694 | var integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat); | ||
| 695 | |||
| 696 | if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) | ||
| 697 | { | ||
| 698 | this.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); | ||
| 699 | } | ||
| 700 | else if (minimum > integer || maximum < integer) | ||
| 701 | { | ||
| 702 | this.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); | ||
| 703 | integer = CompilerConstants.IllegalInteger; | ||
| 704 | } | ||
| 705 | |||
| 706 | return value; | ||
| 707 | } | ||
| 708 | catch (FormatException) | ||
| 709 | { | ||
| 710 | this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 711 | } | ||
| 712 | catch (OverflowException) | ||
| 713 | { | ||
| 714 | this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 715 | } | ||
| 716 | } | ||
| 717 | } | ||
| 718 | |||
| 719 | return null; | ||
| 720 | } | ||
| 721 | |||
| 722 | /// <summary> | ||
| 723 | /// Get a guid attribute value and displays an error for an illegal guid value. | ||
| 724 | /// </summary> | ||
| 725 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 726 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 727 | /// <param name="generatable">Determines whether the guid can be automatically generated.</param> | ||
| 728 | /// <param name="canBeEmpty">If true, no error is raised on empty value. If false, an error is raised.</param> | ||
| 729 | /// <returns>The attribute's guid value or a special value if an error occurred.</returns> | ||
| 730 | public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) | ||
| 731 | { | ||
| 732 | return this.parseHelper.GetAttributeGuidValue(sourceLineNumbers, attribute, generatable, canBeEmpty); | ||
| 733 | } | ||
| 734 | |||
| 735 | /// <summary> | ||
| 736 | /// Get an identifier attribute value and displays an error for an illegal identifier value. | ||
| 737 | /// </summary> | ||
| 738 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 739 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 740 | /// <returns>The attribute's identifier value or a special value if an error occurred.</returns> | ||
| 741 | public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 742 | { | ||
| 743 | return this.parseHelper.GetAttributeIdentifier(sourceLineNumbers, attribute); | ||
| 744 | } | ||
| 745 | |||
| 746 | /// <summary> | ||
| 747 | /// Get an identifier attribute value and displays an error for an illegal identifier value. | ||
| 748 | /// </summary> | ||
| 749 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 750 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 751 | /// <returns>The attribute's identifier value or a special value if an error occurred.</returns> | ||
| 752 | public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 753 | { | ||
| 754 | return this.parseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attribute); | ||
| 755 | } | ||
| 756 | |||
| 757 | /// <summary> | ||
| 758 | /// Gets a yes/no value and displays an error for an illegal yes/no value. | ||
| 759 | /// </summary> | ||
| 760 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 761 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 762 | /// <returns>The attribute's YesNoType value.</returns> | ||
| 763 | public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 764 | { | ||
| 765 | return this.parseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute); | ||
| 766 | } | ||
| 767 | |||
| 768 | /// <summary> | ||
| 769 | /// Gets a yes/no/default value and displays an error for an illegal yes/no value. | ||
| 770 | /// </summary> | ||
| 771 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 772 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 773 | /// <returns>The attribute's YesNoDefaultType value.</returns> | ||
| 774 | public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 775 | { | ||
| 776 | return this.parseHelper.GetAttributeYesNoDefaultValue(sourceLineNumbers, attribute); | ||
| 777 | } | ||
| 778 | |||
| 779 | /// <summary> | ||
| 780 | /// Gets a short filename value and displays an error for an illegal short filename value. | ||
| 781 | /// </summary> | ||
| 782 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 783 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 784 | /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param> | ||
| 785 | /// <returns>The attribute's short filename value.</returns> | ||
| 786 | public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false) | ||
| 787 | { | ||
| 788 | if (null == attribute) | ||
| 789 | { | ||
| 790 | throw new ArgumentNullException("attribute"); | ||
| 791 | } | ||
| 792 | |||
| 793 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 794 | |||
| 795 | if (0 < value.Length) | ||
| 796 | { | ||
| 797 | if (!this.parseHelper.IsValidShortFilename(value, allowWildcards) && !Common.ContainsValidBinderVariable(value)) | ||
| 798 | { | ||
| 799 | this.Write(ErrorMessages.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 800 | } | ||
| 801 | else if (CompilerCore.IsAmbiguousFilename(value)) | ||
| 802 | { | ||
| 803 | this.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 804 | } | ||
| 805 | } | ||
| 806 | |||
| 807 | return value; | ||
| 808 | } | ||
| 809 | |||
| 810 | /// <summary> | ||
| 811 | /// Gets a long filename value and displays an error for an illegal long filename value. | ||
| 812 | /// </summary> | ||
| 813 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 814 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 815 | /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param> | ||
| 816 | /// <param name="allowRelative">true if relative paths are allowed in the filename.</param> | ||
| 817 | /// <returns>The attribute's long filename value.</returns> | ||
| 818 | public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false) | ||
| 819 | { | ||
| 820 | return this.parseHelper.GetAttributeLongFilename(sourceLineNumbers, attribute, allowWildcards, allowRelative); | ||
| 821 | } | ||
| 822 | |||
| 823 | /// <summary> | ||
| 824 | /// Gets a version value or possibly a binder variable and displays an error for an illegal version value. | ||
| 825 | /// </summary> | ||
| 826 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 827 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 828 | /// <returns>The attribute's version value.</returns> | ||
| 829 | public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 830 | { | ||
| 831 | return this.parseHelper.GetAttributeVersionValue(sourceLineNumbers, attribute); | ||
| 832 | } | ||
| 833 | |||
| 834 | /// <summary> | ||
| 835 | /// Gets a RegistryRoot as a MsiInterop.MsidbRegistryRoot value and displays an error for an illegal value. | ||
| 836 | /// </summary> | ||
| 837 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 838 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 839 | /// <param name="allowHkmu">Whether HKMU is returned as -1 (true), or treated as an error (false).</param> | ||
| 840 | /// <returns>The attribute's RegisitryRootType value.</returns> | ||
| 841 | public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) | ||
| 842 | { | ||
| 843 | return this.parseHelper.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu); | ||
| 844 | } | ||
| 845 | |||
| 846 | /// <summary> | ||
| 847 | /// Gets a Bundle variable value and displays an error for an illegal value. | ||
| 848 | /// </summary> | ||
| 849 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 850 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 851 | /// <returns>The attribute's value.</returns> | ||
| 852 | public string GetAttributeBundleVariableValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 853 | { | ||
| 854 | string value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 855 | |||
| 856 | if (!String.IsNullOrEmpty(value)) | ||
| 857 | { | ||
| 858 | if (CompilerCore.BuiltinBundleVariables.Contains(value)) | ||
| 859 | { | ||
| 860 | string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables); | ||
| 861 | this.Write(ErrorMessages.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues)); | ||
| 862 | } | ||
| 863 | } | ||
| 864 | |||
| 865 | return value; | ||
| 866 | } | ||
| 867 | |||
| 868 | /// <summary> | ||
| 869 | /// Gets an MsiProperty name value and displays an error for an illegal value. | ||
| 870 | /// </summary> | ||
| 871 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 872 | /// <param name="attribute">The attribute containing the value to get.</param> | ||
| 873 | /// <returns>The attribute's value.</returns> | ||
| 874 | public string GetAttributeMsiPropertyNameValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 875 | { | ||
| 876 | string value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 877 | |||
| 878 | if (0 < value.Length) | ||
| 879 | { | ||
| 880 | if (CompilerCore.DisallowedMsiProperties.Contains(value)) | ||
| 881 | { | ||
| 882 | string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties); | ||
| 883 | this.Write(ErrorMessages.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues)); | ||
| 884 | } | ||
| 885 | } | ||
| 886 | |||
| 887 | return value; | ||
| 888 | } | ||
| 889 | |||
| 890 | /// <summary> | ||
| 891 | /// Checks if the string contains a property (i.e. "foo[Property]bar") | ||
| 892 | /// </summary> | ||
| 893 | /// <param name="possibleProperty">String to evaluate for properties.</param> | ||
| 894 | /// <returns>True if a property is found in the string.</returns> | ||
| 895 | public bool ContainsProperty(string possibleProperty) | ||
| 896 | { | ||
| 897 | return this.parseHelper.ContainsProperty(possibleProperty); | ||
| 898 | } | ||
| 899 | |||
| 900 | /// <summary> | ||
| 901 | /// Generate an identifier by hashing data from the row. | ||
| 902 | /// </summary> | ||
| 903 | /// <param name="prefix">Three letter or less prefix for generated row identifier.</param> | ||
| 904 | /// <param name="args">Information to hash.</param> | ||
| 905 | /// <returns>The generated identifier.</returns> | ||
| 906 | public Identifier CreateIdentifier(string prefix, params string[] args) | ||
| 907 | { | ||
| 908 | return this.parseHelper.CreateIdentifier(prefix, args); | ||
| 909 | } | ||
| 910 | |||
| 911 | /// <summary> | ||
| 912 | /// Create an identifier based on passed file name | ||
| 913 | /// </summary> | ||
| 914 | /// <param name="filename">File name to generate identifer from</param> | ||
| 915 | /// <returns></returns> | ||
| 916 | public Identifier CreateIdentifierFromFilename(string filename) | ||
| 917 | { | ||
| 918 | return this.parseHelper.CreateIdentifierFromFilename(filename); | ||
| 919 | } | ||
| 920 | |||
| 921 | /// <summary> | ||
| 922 | /// Attempts to use an extension to parse the attribute. | ||
| 923 | /// </summary> | ||
| 924 | /// <param name="element">Element containing attribute to be parsed.</param> | ||
| 925 | /// <param name="attribute">Attribute to be parsed.</param> | ||
| 926 | /// <param name="context">Extra information about the context in which this element is being parsed.</param> | ||
| 927 | public void ParseExtensionAttribute(XElement element, XAttribute attribute, IDictionary<string, string> context = null) | ||
| 928 | { | ||
| 929 | this.parseHelper.ParseExtensionAttribute(this.extensions.Values, this.intermediate, this.ActiveSection, element, attribute, context); | ||
| 930 | } | ||
| 931 | |||
| 932 | /// <summary> | ||
| 933 | /// Attempts to use an extension to parse the element. | ||
| 934 | /// </summary> | ||
| 935 | /// <param name="parentElement">Element containing element to be parsed.</param> | ||
| 936 | /// <param name="element">Element to be parsed.</param> | ||
| 937 | /// <param name="context">Extra information about the context in which this element is being parsed.</param> | ||
| 938 | public void ParseExtensionElement(XElement parentElement, XElement element, IDictionary<string, string> context = null) | ||
| 939 | { | ||
| 940 | this.parseHelper.ParseExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); | ||
| 941 | } | ||
| 942 | |||
| 943 | /// <summary> | ||
| 944 | /// Process all children of the element looking for extensions and erroring on the unexpected. | ||
| 945 | /// </summary> | ||
| 946 | /// <param name="element">Element to parse children.</param> | ||
| 947 | public void ParseForExtensionElements(XElement element) | ||
| 948 | { | ||
| 949 | this.parseHelper.ParseForExtensionElements(this.extensions.Values, this.intermediate, this.ActiveSection, element); | ||
| 950 | } | ||
| 951 | |||
| 952 | /// <summary> | ||
| 953 | /// Attempts to use an extension to parse the element, with support for setting component keypath. | ||
| 954 | /// </summary> | ||
| 955 | /// <param name="parentElement">Element containing element to be parsed.</param> | ||
| 956 | /// <param name="element">Element to be parsed.</param> | ||
| 957 | /// <param name="context">Extra information about the context in which this element is being parsed.</param> | ||
| 958 | public IComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary<string, string> context) | ||
| 959 | { | ||
| 960 | return this.parseHelper.ParsePossibleKeyPathExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); | ||
| 961 | } | ||
| 962 | |||
| 963 | /// <summary> | ||
| 964 | /// Displays an unexpected attribute error if the attribute is not the namespace attribute. | ||
| 965 | /// </summary> | ||
| 966 | /// <param name="element">Element containing unexpected attribute.</param> | ||
| 967 | /// <param name="attribute">The unexpected attribute.</param> | ||
| 968 | public void UnexpectedAttribute(XElement element, XAttribute attribute) | ||
| 969 | { | ||
| 970 | this.parseHelper.UnexpectedAttribute(element, attribute); | ||
| 971 | } | ||
| 972 | |||
| 973 | /// <summary> | ||
| 974 | /// Display an unexepected element error. | ||
| 975 | /// </summary> | ||
| 976 | /// <param name="parentElement">The parent element.</param> | ||
| 977 | /// <param name="childElement">The unexpected child element.</param> | ||
| 978 | public void UnexpectedElement(XElement parentElement, XElement childElement) | ||
| 979 | { | ||
| 980 | this.parseHelper.UnexpectedElement(parentElement, childElement); | ||
| 981 | } | ||
| 982 | |||
| 983 | /// <summary> | ||
| 984 | /// Sends a message. | ||
| 985 | /// </summary> | ||
| 986 | /// <param name="message">Message to write.</param> | ||
| 987 | public void Write(Message message) | ||
| 988 | { | ||
| 989 | this.messaging.Write(message); | ||
| 990 | } | ||
| 991 | |||
| 992 | /// <summary> | ||
| 993 | /// Verifies that the calling assembly version is equal to or newer than the given <paramref name="requiredVersion"/>. | ||
| 994 | /// </summary> | ||
| 995 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | ||
| 996 | /// <param name="requiredVersion">The version required of the calling assembly.</param> | ||
| 997 | internal void VerifyRequiredVersion(SourceLineNumber sourceLineNumbers, string requiredVersion) | ||
| 998 | { | ||
| 999 | // an null or empty string means any version will work | ||
| 1000 | if (!String.IsNullOrEmpty(requiredVersion)) | ||
| 1001 | { | ||
| 1002 | Assembly caller = Assembly.GetCallingAssembly(); | ||
| 1003 | AssemblyName name = caller.GetName(); | ||
| 1004 | FileVersionInfo fv = FileVersionInfo.GetVersionInfo(caller.Location); | ||
| 1005 | |||
| 1006 | Version versionRequired = new Version(requiredVersion); | ||
| 1007 | Version versionCurrent = new Version(fv.FileVersion); | ||
| 1008 | |||
| 1009 | if (versionRequired > versionCurrent) | ||
| 1010 | { | ||
| 1011 | if (this.GetType().Assembly.Equals(caller)) | ||
| 1012 | { | ||
| 1013 | this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired)); | ||
| 1014 | } | ||
| 1015 | else | ||
| 1016 | { | ||
| 1017 | this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired, name.Name)); | ||
| 1018 | } | ||
| 1019 | } | ||
| 1020 | } | ||
| 1021 | } | ||
| 1022 | |||
| 1023 | /// <summary> | ||
| 1024 | /// Creates a new section and makes it the active section in the core. | ||
| 1025 | /// </summary> | ||
| 1026 | /// <param name="id">Unique identifier for the section.</param> | ||
| 1027 | /// <param name="type">Type of section to create.</param> | ||
| 1028 | /// <param name="compilationId">Unique identifier for the compilation.</param> | ||
| 1029 | /// <returns>New section.</returns> | ||
| 1030 | internal IntermediateSection CreateActiveSection(string id, SectionType type, string compilationId) | ||
| 1031 | { | ||
| 1032 | this.ActiveSection = this.CreateSection(id, type, compilationId); | ||
| 1033 | |||
| 1034 | this.activeSectionCachedInlinedDirectoryIds = new Dictionary<string, string>(); | ||
| 1035 | this.activeSectionSimpleReferences = new HashSet<string>(); | ||
| 1036 | |||
| 1037 | return this.ActiveSection; | ||
| 1038 | } | ||
| 1039 | |||
| 1040 | /// <summary> | ||
| 1041 | /// Creates a new section. | ||
| 1042 | /// </summary> | ||
| 1043 | /// <param name="id">Unique identifier for the section.</param> | ||
| 1044 | /// <param name="type">Type of section to create.</param> | ||
| 1045 | /// <param name="compilationId">Unique identifier for the compilation.</param> | ||
| 1046 | /// <returns>New section.</returns> | ||
| 1047 | internal IntermediateSection CreateSection(string id, SectionType type, string compilationId) | ||
| 1048 | { | ||
| 1049 | var section = new IntermediateSection(id, type, compilationId); | ||
| 1050 | |||
| 1051 | this.intermediate.AddSection(section); | ||
| 1052 | |||
| 1053 | return section; | ||
| 1054 | } | ||
| 1055 | |||
| 1056 | /// <summary> | ||
| 1057 | /// Creates WixComplexReference and WixGroup rows in the active section. | ||
| 1058 | /// </summary> | ||
| 1059 | /// <param name="sourceLineNumbers">Source line information.</param> | ||
| 1060 | /// <param name="parentType">The parent type.</param> | ||
| 1061 | /// <param name="parentId">The parent id.</param> | ||
| 1062 | /// <param name="parentLanguage">The parent language.</param> | ||
| 1063 | /// <param name="childType">The child type.</param> | ||
| 1064 | /// <param name="childId">The child id.</param> | ||
| 1065 | /// <param name="isPrimary">Whether the child is primary.</param> | ||
| 1066 | public void CreateComplexReference(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) | ||
| 1067 | { | ||
| 1068 | this.parseHelper.CreateComplexReference(this.ActiveSection, sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary); | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | /// <summary> | ||
| 1072 | /// Creates a directory row from a name. | ||
| 1073 | /// </summary> | ||
| 1074 | /// <param name="sourceLineNumbers">Source line information.</param> | ||
| 1075 | /// <param name="id">Optional identifier for the new row.</param> | ||
| 1076 | /// <param name="parentId">Optional identifier for the parent row.</param> | ||
| 1077 | /// <param name="name">Long name of the directory.</param> | ||
| 1078 | /// <param name="shortName">Optional short name of the directory.</param> | ||
| 1079 | /// <param name="sourceName">Optional source name for the directory.</param> | ||
| 1080 | /// <param name="shortSourceName">Optional short source name for the directory.</param> | ||
| 1081 | /// <returns>Identifier for the newly created row.</returns> | ||
| 1082 | internal Identifier CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) | ||
| 1083 | { | ||
| 1084 | return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName); | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) | ||
| 1088 | { | ||
| 1089 | this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false) | ||
| 1093 | { | ||
| 1094 | return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); | ||
| 1095 | } | ||
| 1096 | |||
| 1097 | private static string CreateValueList(ValueListKind kind, IEnumerable<string> values) | ||
| 1098 | { | ||
| 1099 | // Ideally, we could denote the list kind (and the list itself) directly in the | ||
| 1100 | // message XML, and detect and expand in the MessageHandler.GenerateMessageString() | ||
| 1101 | // method. Doing so would make vararg-style messages much easier, but impacts | ||
| 1102 | // every single message we format. For now, callers just have to know when a | ||
| 1103 | // message takes a list of values in a single string argument, the caller will | ||
| 1104 | // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge | ||
| 1105 | // that the list is an 'and' or 'or' list.) | ||
| 1106 | |||
| 1107 | // For a localizable solution, we need to be able to get the list format string | ||
| 1108 | // from resources. We aren't currently localized right now, so the values are | ||
| 1109 | // just hard-coded. | ||
| 1110 | const string valueFormat = "'{0}'"; | ||
| 1111 | const string valueSeparator = ", "; | ||
| 1112 | string terminalTerm = String.Empty; | ||
| 1113 | |||
| 1114 | switch (kind) | ||
| 1115 | { | ||
| 1116 | case ValueListKind.None: | ||
| 1117 | terminalTerm = ""; | ||
| 1118 | break; | ||
| 1119 | case ValueListKind.And: | ||
| 1120 | terminalTerm = "and "; | ||
| 1121 | break; | ||
| 1122 | case ValueListKind.Or: | ||
| 1123 | terminalTerm = "or "; | ||
| 1124 | break; | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | StringBuilder list = new StringBuilder(); | ||
| 1128 | |||
| 1129 | // This weird construction helps us determine when we're adding the last value | ||
| 1130 | // to the list. Instead of adding them as we encounter them, we cache the current | ||
| 1131 | // value and append the *previous* one. | ||
| 1132 | string previousValue = null; | ||
| 1133 | bool haveValues = false; | ||
| 1134 | foreach (string value in values) | ||
| 1135 | { | ||
| 1136 | if (null != previousValue) | ||
| 1137 | { | ||
| 1138 | if (haveValues) | ||
| 1139 | { | ||
| 1140 | list.Append(valueSeparator); | ||
| 1141 | } | ||
| 1142 | list.AppendFormat(valueFormat, previousValue); | ||
| 1143 | haveValues = true; | ||
| 1144 | } | ||
| 1145 | |||
| 1146 | previousValue = value; | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | // If we have no previous value, that means that the list contained no values, and | ||
| 1150 | // something has gone very wrong. | ||
| 1151 | Debug.Assert(null != previousValue); | ||
| 1152 | if (null != previousValue) | ||
| 1153 | { | ||
| 1154 | if (haveValues) | ||
| 1155 | { | ||
| 1156 | list.Append(valueSeparator); | ||
| 1157 | list.Append(terminalTerm); | ||
| 1158 | } | ||
| 1159 | list.AppendFormat(valueFormat, previousValue); | ||
| 1160 | haveValues = true; | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | return list.ToString(); | ||
| 1164 | } | ||
| 1165 | } | ||
| 1166 | } | ||
diff --git a/src/wix/WixToolset.Core/CompilerErrors.cs b/src/wix/WixToolset.Core/CompilerErrors.cs new file mode 100644 index 00000000..10646dfd --- /dev/null +++ b/src/wix/WixToolset.Core/CompilerErrors.cs | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | internal static class CompilerErrors | ||
| 8 | { | ||
| 9 | public static Message IllegalCharactersInProvider(SourceLineNumber sourceLineNumbers, string attributeName, char illegalChar, string illegalChars) | ||
| 10 | { | ||
| 11 | return Message(sourceLineNumbers, Ids.IllegalCharactersInProvider, "The provider key authored into the {0} attribute contains an illegal character, '{1}'. Please author the provider key without any of the following characters: {2}", attributeName, illegalChar, illegalChars); | ||
| 12 | } | ||
| 13 | |||
| 14 | public static Message ReservedValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string attributeValue) | ||
| 15 | { | ||
| 16 | return Message(sourceLineNumbers, Ids.ReservedValue, "The {0}/@{1} attribute value '{2}' is reserved and cannot be used here. Please choose a different value.", elementName, attributeName, attributeValue); | ||
| 17 | } | ||
| 18 | |||
| 19 | public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name) | ||
| 20 | { | ||
| 21 | return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name); | ||
| 22 | } | ||
| 23 | |||
| 24 | public static Message ExampleRegid(SourceLineNumber sourceLineNumbers, string regid) | ||
| 25 | { | ||
| 26 | return Message(sourceLineNumbers, Ids.ExampleRegid, "Regid '{0}' is a placeholder that must be replaced with an appropriate value for your installation. Use the simplified URI for your organization or project.", regid); | ||
| 27 | } | ||
| 28 | |||
| 29 | private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) | ||
| 30 | { | ||
| 31 | return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); | ||
| 32 | } | ||
| 33 | |||
| 34 | public enum Ids | ||
| 35 | { | ||
| 36 | IllegalCharactersInProvider = 5400, | ||
| 37 | ReservedValue = 5401, | ||
| 38 | |||
| 39 | IllegalName = 6601, | ||
| 40 | ExampleRegid = 6602, | ||
| 41 | } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. | ||
| 42 | } | ||
| 43 | } | ||
diff --git a/src/wix/WixToolset.Core/CompilerWarnings.cs b/src/wix/WixToolset.Core/CompilerWarnings.cs new file mode 100644 index 00000000..5c11b878 --- /dev/null +++ b/src/wix/WixToolset.Core/CompilerWarnings.cs | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | internal static class CompilerWarnings | ||
| 8 | { | ||
| 9 | public static Message DirectoryRefStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) | ||
| 10 | { | ||
| 11 | return Message(sourceLineNumbers, Ids.DirectoryRefStandardDirectoryDeprecated, "Using DirectoryRef to reference the standard directory '{0}' is deprecated. Use the StandardDirectory element instead.", directoryId); | ||
| 12 | } | ||
| 13 | |||
| 14 | public static Message DefiningStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) | ||
| 15 | { | ||
| 16 | return Message(sourceLineNumbers, Ids.DefiningStandardDirectoryDeprecated, "It is no longer necessary to define the standard directory '{0}'. Use the StandardDirectory element instead.", directoryId); | ||
| 17 | } | ||
| 18 | |||
| 19 | public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers) | ||
| 20 | { | ||
| 21 | return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified in an MSI package. The ProductVersion will be used by default."); | ||
| 22 | } | ||
| 23 | |||
| 24 | public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers, string id) | ||
| 25 | { | ||
| 26 | return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified for MSI package {0}. The ProductVersion will be used by default.", id); | ||
| 27 | } | ||
| 28 | |||
| 29 | public static Message PropertyRemoved(string name) | ||
| 30 | { | ||
| 31 | return Message(null, Ids.PropertyRemoved, "The property {0} was authored in the package with a value and will be removed. The property should not be authored.", name); | ||
| 32 | } | ||
| 33 | |||
| 34 | public static Message ProvidesKeyNotFound(SourceLineNumber sourceLineNumbers, string id) | ||
| 35 | { | ||
| 36 | return Message(sourceLineNumbers, Ids.ProvidesKeyNotFound, "The provider key with identifier {0} was not found in the WixDependencyProvider table. Related registry rows will not be removed from authoring.", id); | ||
| 37 | } | ||
| 38 | |||
| 39 | public static Message RequiresKeyNotFound(SourceLineNumber sourceLineNumbers, string id) | ||
| 40 | { | ||
| 41 | return Message(sourceLineNumbers, Ids.RequiresKeyNotFound, "The dependency key with identifier {0} was not found in the WixDependency table. Related registry rows will not be removed from authoring.", id); | ||
| 42 | } | ||
| 43 | |||
| 44 | public static Message Win64Component(SourceLineNumber sourceLineNumbers, string componentId) | ||
| 45 | { | ||
| 46 | return Message(sourceLineNumbers, Ids.Win64Component, "The Provides element should not be authored in the 64-bit component with identifier {0}. The dependency feature may not work if installing this package on 64-bit Windows operating systems prior to Windows 7 and Windows Server 2008 R2. Set the Component/@Bitness attribute to \"always32\" to ensure the dependency feature works correctly on legacy operating systems.", componentId); | ||
| 47 | } | ||
| 48 | |||
| 49 | private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) | ||
| 50 | { | ||
| 51 | return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); | ||
| 52 | } | ||
| 53 | |||
| 54 | public enum Ids | ||
| 55 | { | ||
| 56 | ProvidesKeyNotFound = 5431, | ||
| 57 | RequiresKeyNotFound = 5432, | ||
| 58 | PropertyRemoved = 5433, | ||
| 59 | DiscouragedVersionAttribute = 5434, | ||
| 60 | Win64Component = 5435, | ||
| 61 | DirectoryRefStandardDirectoryDeprecated = 5436, | ||
| 62 | DefiningStandardDirectoryDeprecated = 5437, | ||
| 63 | } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. | ||
| 64 | } | ||
| 65 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler_Bundle.cs b/src/wix/WixToolset.Core/Compiler_Bundle.cs new file mode 100644 index 00000000..6d2e75f7 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Bundle.cs | |||
| @@ -0,0 +1,3266 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using System.Xml.Linq; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Burn; | ||
| 14 | using WixToolset.Data.Symbols; | ||
| 15 | using WixToolset.Extensibility; | ||
| 16 | |||
| 17 | /// <summary> | ||
| 18 | /// Compiler of the WiX toolset. | ||
| 19 | /// </summary> | ||
| 20 | internal partial class Compiler : ICompiler | ||
| 21 | { | ||
| 22 | private static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnUXContainerName); | ||
| 23 | private static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnDefaultAttachedContainerName); | ||
| 24 | private static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Section, BurnConstants.BundleLayoutOnlyPayloadsName); | ||
| 25 | |||
| 26 | /// <summary> | ||
| 27 | /// Parses an ApprovedExeForElevation element. | ||
| 28 | /// </summary> | ||
| 29 | /// <param name="node">Element to parse</param> | ||
| 30 | private void ParseApprovedExeForElevation(XElement node) | ||
| 31 | { | ||
| 32 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 33 | Identifier id = null; | ||
| 34 | string key = null; | ||
| 35 | string valueName = null; | ||
| 36 | var win64 = this.Context.IsCurrentPlatform64Bit; | ||
| 37 | |||
| 38 | foreach (var attrib in node.Attributes()) | ||
| 39 | { | ||
| 40 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 41 | { | ||
| 42 | switch (attrib.Name.LocalName) | ||
| 43 | { | ||
| 44 | case "Id": | ||
| 45 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 46 | break; | ||
| 47 | case "Bitness": | ||
| 48 | var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 49 | switch (bitnessValue) | ||
| 50 | { | ||
| 51 | case "always32": | ||
| 52 | win64 = false; | ||
| 53 | break; | ||
| 54 | case "always64": | ||
| 55 | win64 = true; | ||
| 56 | break; | ||
| 57 | case "default": | ||
| 58 | case "": | ||
| 59 | break; | ||
| 60 | default: | ||
| 61 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); | ||
| 62 | break; | ||
| 63 | } | ||
| 64 | break; | ||
| 65 | case "Key": | ||
| 66 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 67 | break; | ||
| 68 | case "Value": | ||
| 69 | valueName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 70 | break; | ||
| 71 | default: | ||
| 72 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 73 | break; | ||
| 74 | } | ||
| 75 | } | ||
| 76 | else | ||
| 77 | { | ||
| 78 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | if (null == id) | ||
| 83 | { | ||
| 84 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 85 | } | ||
| 86 | |||
| 87 | if (null == key) | ||
| 88 | { | ||
| 89 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 90 | } | ||
| 91 | |||
| 92 | var attributes = WixApprovedExeForElevationAttributes.None; | ||
| 93 | |||
| 94 | if (win64) | ||
| 95 | { | ||
| 96 | attributes |= WixApprovedExeForElevationAttributes.Win64; | ||
| 97 | } | ||
| 98 | |||
| 99 | this.Core.ParseForExtensionElements(node); | ||
| 100 | |||
| 101 | if (!this.Core.EncounteredError) | ||
| 102 | { | ||
| 103 | this.Core.AddSymbol(new WixApprovedExeForElevationSymbol(sourceLineNumbers, id) | ||
| 104 | { | ||
| 105 | Key = key, | ||
| 106 | ValueName = valueName, | ||
| 107 | Attributes = attributes, | ||
| 108 | }); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | /// <summary> | ||
| 113 | /// Parses a Bundle element. | ||
| 114 | /// </summary> | ||
| 115 | /// <param name="node">Element to parse</param> | ||
| 116 | private void ParseBundleElement(XElement node) | ||
| 117 | { | ||
| 118 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 119 | string copyright = null; | ||
| 120 | string aboutUrl = null; | ||
| 121 | var compressed = YesNoDefaultType.Default; | ||
| 122 | WixBundleAttributes attributes = 0; | ||
| 123 | string helpTelephone = null; | ||
| 124 | string helpUrl = null; | ||
| 125 | string manufacturer = null; | ||
| 126 | string name = null; | ||
| 127 | string tag = null; | ||
| 128 | string updateUrl = null; | ||
| 129 | string upgradeCode = null; | ||
| 130 | string version = null; | ||
| 131 | string condition = null; | ||
| 132 | string parentName = null; | ||
| 133 | |||
| 134 | string fileSystemSafeBundleName = null; | ||
| 135 | string logVariablePrefixAndExtension = null; | ||
| 136 | string iconSourceFile = null; | ||
| 137 | string splashScreenSourceFile = null; | ||
| 138 | |||
| 139 | // Process only standard attributes until the active section is initialized. | ||
| 140 | foreach (var attrib in node.Attributes()) | ||
| 141 | { | ||
| 142 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 143 | { | ||
| 144 | switch (attrib.Name.LocalName) | ||
| 145 | { | ||
| 146 | case "AboutUrl": | ||
| 147 | aboutUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 148 | break; | ||
| 149 | case "Compressed": | ||
| 150 | compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
| 151 | break; | ||
| 152 | case "Condition": | ||
| 153 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 154 | break; | ||
| 155 | case "Copyright": | ||
| 156 | copyright = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 157 | break; | ||
| 158 | case "DisableModify": | ||
| 159 | var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 160 | switch (value) | ||
| 161 | { | ||
| 162 | case "button": | ||
| 163 | attributes |= WixBundleAttributes.SingleChangeUninstallButton; | ||
| 164 | break; | ||
| 165 | case "yes": | ||
| 166 | attributes |= WixBundleAttributes.DisableModify; | ||
| 167 | break; | ||
| 168 | case "no": | ||
| 169 | break; | ||
| 170 | default: | ||
| 171 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); | ||
| 172 | break; | ||
| 173 | } | ||
| 174 | break; | ||
| 175 | case "DisableRemove": | ||
| 176 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 177 | { | ||
| 178 | attributes |= WixBundleAttributes.DisableRemove; | ||
| 179 | } | ||
| 180 | break; | ||
| 181 | case "HelpTelephone": | ||
| 182 | helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 183 | break; | ||
| 184 | case "HelpUrl": | ||
| 185 | helpUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 186 | break; | ||
| 187 | case "Manufacturer": | ||
| 188 | manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 189 | break; | ||
| 190 | case "IconSourceFile": | ||
| 191 | iconSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 192 | break; | ||
| 193 | case "Name": | ||
| 194 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 195 | break; | ||
| 196 | case "ParentName": | ||
| 197 | parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 198 | break; | ||
| 199 | case "ProviderKey": | ||
| 200 | // This can't be processed until we create the section. | ||
| 201 | break; | ||
| 202 | case "SplashScreenSourceFile": | ||
| 203 | splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 204 | break; | ||
| 205 | case "Tag": | ||
| 206 | tag = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 207 | break; | ||
| 208 | case "UpdateUrl": | ||
| 209 | updateUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 210 | break; | ||
| 211 | case "UpgradeCode": | ||
| 212 | upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 213 | break; | ||
| 214 | case "Version": | ||
| 215 | version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 216 | break; | ||
| 217 | default: | ||
| 218 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 219 | break; | ||
| 220 | } | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | if (String.IsNullOrEmpty(version)) | ||
| 225 | { | ||
| 226 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
| 227 | } | ||
| 228 | else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) | ||
| 229 | { | ||
| 230 | this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version)); | ||
| 231 | } | ||
| 232 | |||
| 233 | if (String.IsNullOrEmpty(upgradeCode)) | ||
| 234 | { | ||
| 235 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpgradeCode")); | ||
| 236 | } | ||
| 237 | |||
| 238 | if (String.IsNullOrEmpty(copyright)) | ||
| 239 | { | ||
| 240 | if (String.IsNullOrEmpty(manufacturer)) | ||
| 241 | { | ||
| 242 | copyright = "Copyright (c). All rights reserved."; | ||
| 243 | } | ||
| 244 | else | ||
| 245 | { | ||
| 246 | copyright = String.Format("Copyright (c) {0}. All rights reserved.", manufacturer); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | if (String.IsNullOrEmpty(name)) | ||
| 251 | { | ||
| 252 | logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup:log"); | ||
| 253 | } | ||
| 254 | else | ||
| 255 | { | ||
| 256 | // Ensure only allowable path characters are in "name" (and change spaces to underscores). | ||
| 257 | fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), '_'); | ||
| 258 | logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log"); | ||
| 259 | } | ||
| 260 | |||
| 261 | this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; | ||
| 262 | this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, this.Context.CompilationId); | ||
| 263 | |||
| 264 | // Now that the active section is initialized, process only extension attributes and the special ProviderKey attribute. | ||
| 265 | foreach (var attrib in node.Attributes()) | ||
| 266 | { | ||
| 267 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 268 | { | ||
| 269 | switch (attrib.Name.LocalName) | ||
| 270 | { | ||
| 271 | case "ProviderKey": | ||
| 272 | this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib); | ||
| 273 | break; | ||
| 274 | // Unknown attributes were reported earlier. | ||
| 275 | } | ||
| 276 | } | ||
| 277 | else | ||
| 278 | { | ||
| 279 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | var baSeen = false; | ||
| 284 | var chainSeen = false; | ||
| 285 | var logSeen = false; | ||
| 286 | |||
| 287 | foreach (var child in node.Elements()) | ||
| 288 | { | ||
| 289 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 290 | { | ||
| 291 | switch (child.Name.LocalName) | ||
| 292 | { | ||
| 293 | case "ApprovedExeForElevation": | ||
| 294 | this.ParseApprovedExeForElevation(child); | ||
| 295 | break; | ||
| 296 | case "BootstrapperApplication": | ||
| 297 | if (baSeen) | ||
| 298 | { | ||
| 299 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 300 | this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "BootstrapperApplication")); | ||
| 301 | } | ||
| 302 | this.ParseBootstrapperApplicationElement(child); | ||
| 303 | baSeen = true; | ||
| 304 | break; | ||
| 305 | case "BootstrapperApplicationRef": | ||
| 306 | this.ParseBootstrapperApplicationRefElement(child); | ||
| 307 | break; | ||
| 308 | case "BundleCustomData": | ||
| 309 | this.ParseBundleCustomDataElement(child); | ||
| 310 | break; | ||
| 311 | case "BundleCustomDataRef": | ||
| 312 | this.ParseBundleCustomDataRefElement(child); | ||
| 313 | break; | ||
| 314 | case "BundleExtension": | ||
| 315 | this.ParseBundleExtensionElement(child); | ||
| 316 | break; | ||
| 317 | case "BundleExtensionRef": | ||
| 318 | this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); | ||
| 319 | break; | ||
| 320 | case "OptionalUpdateRegistration": | ||
| 321 | this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); | ||
| 322 | break; | ||
| 323 | case "Chain": | ||
| 324 | if (chainSeen) | ||
| 325 | { | ||
| 326 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 327 | this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Chain")); | ||
| 328 | } | ||
| 329 | this.ParseChainElement(child); | ||
| 330 | chainSeen = true; | ||
| 331 | break; | ||
| 332 | case "Container": | ||
| 333 | this.ParseContainerElement(child); | ||
| 334 | break; | ||
| 335 | case "ContainerRef": | ||
| 336 | this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleContainer); | ||
| 337 | break; | ||
| 338 | case "Log": | ||
| 339 | if (logSeen) | ||
| 340 | { | ||
| 341 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 342 | this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Log")); | ||
| 343 | } | ||
| 344 | logVariablePrefixAndExtension = this.ParseLogElement(child, fileSystemSafeBundleName); | ||
| 345 | logSeen = true; | ||
| 346 | break; | ||
| 347 | case "PayloadGroup": | ||
| 348 | this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads); | ||
| 349 | break; | ||
| 350 | case "PayloadGroupRef": | ||
| 351 | this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads, ComplexReferenceChildType.Unknown, null); | ||
| 352 | break; | ||
| 353 | case "RelatedBundle": | ||
| 354 | this.ParseRelatedBundleElement(child); | ||
| 355 | break; | ||
| 356 | case "Requires": | ||
| 357 | this.ParseRequiresElement(child, null); | ||
| 358 | break; | ||
| 359 | case "SetVariable": | ||
| 360 | this.ParseSetVariableElement(child); | ||
| 361 | break; | ||
| 362 | case "SetVariableRef": | ||
| 363 | this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); | ||
| 364 | break; | ||
| 365 | case "SoftwareTag": | ||
| 366 | this.ParseBundleTagElement(child); | ||
| 367 | break; | ||
| 368 | case "Update": | ||
| 369 | this.ParseUpdateElement(child); | ||
| 370 | break; | ||
| 371 | case "Variable": | ||
| 372 | this.ParseVariableElement(child); | ||
| 373 | break; | ||
| 374 | case "WixVariable": | ||
| 375 | this.ParseWixVariableElement(child); | ||
| 376 | break; | ||
| 377 | default: | ||
| 378 | this.Core.UnexpectedElement(node, child); | ||
| 379 | break; | ||
| 380 | } | ||
| 381 | } | ||
| 382 | else | ||
| 383 | { | ||
| 384 | this.Core.ParseExtensionElement(node, child); | ||
| 385 | } | ||
| 386 | } | ||
| 387 | |||
| 388 | if (!chainSeen) | ||
| 389 | { | ||
| 390 | this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Chain")); | ||
| 391 | } | ||
| 392 | |||
| 393 | if (!this.Core.EncounteredError) | ||
| 394 | { | ||
| 395 | var symbol = this.Core.AddSymbol(new WixBundleSymbol(sourceLineNumbers) | ||
| 396 | { | ||
| 397 | UpgradeCode = upgradeCode, | ||
| 398 | Version = version, | ||
| 399 | Copyright = copyright, | ||
| 400 | Name = name, | ||
| 401 | Manufacturer = manufacturer, | ||
| 402 | Attributes = attributes, | ||
| 403 | AboutUrl = aboutUrl, | ||
| 404 | HelpUrl = helpUrl, | ||
| 405 | HelpTelephone = helpTelephone, | ||
| 406 | UpdateUrl = updateUrl, | ||
| 407 | Compressed = YesNoDefaultType.Yes == compressed ? true : YesNoDefaultType.No == compressed ? (bool?)false : null, | ||
| 408 | IconSourceFile = iconSourceFile, | ||
| 409 | SplashScreenSourceFile = splashScreenSourceFile, | ||
| 410 | Condition = condition, | ||
| 411 | Tag = tag, | ||
| 412 | Platform = this.CurrentPlatform, | ||
| 413 | ParentName = parentName, | ||
| 414 | }); | ||
| 415 | |||
| 416 | if (!String.IsNullOrEmpty(logVariablePrefixAndExtension)) | ||
| 417 | { | ||
| 418 | var split = logVariablePrefixAndExtension.Split(':'); | ||
| 419 | symbol.LogPathVariable = split[0]; | ||
| 420 | symbol.LogPrefix = split[1]; | ||
| 421 | symbol.LogExtension = split[2]; | ||
| 422 | } | ||
| 423 | |||
| 424 | if (null != upgradeCode) | ||
| 425 | { | ||
| 426 | this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) | ||
| 427 | { | ||
| 428 | BundleId = upgradeCode, | ||
| 429 | Action = RelatedBundleActionType.Upgrade, | ||
| 430 | }); | ||
| 431 | } | ||
| 432 | |||
| 433 | this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnDefaultAttachedContainerId) | ||
| 434 | { | ||
| 435 | Name = "bundle-attached.cab", | ||
| 436 | Type = ContainerType.Attached, | ||
| 437 | }); | ||
| 438 | |||
| 439 | // Ensure that the bundle stores the well-known persisted values. | ||
| 440 | this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_NAME)) | ||
| 441 | { | ||
| 442 | Hidden = false, | ||
| 443 | Persisted = true, | ||
| 444 | }); | ||
| 445 | |||
| 446 | this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE)) | ||
| 447 | { | ||
| 448 | Hidden = false, | ||
| 449 | Persisted = true, | ||
| 450 | }); | ||
| 451 | |||
| 452 | this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER)) | ||
| 453 | { | ||
| 454 | Hidden = false, | ||
| 455 | Persisted = true, | ||
| 456 | }); | ||
| 457 | |||
| 458 | this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_LAST_USED_SOURCE)) | ||
| 459 | { | ||
| 460 | Hidden = false, | ||
| 461 | Persisted = true, | ||
| 462 | }); | ||
| 463 | } | ||
| 464 | } | ||
| 465 | |||
| 466 | /// <summary> | ||
| 467 | /// Parse a Container element. | ||
| 468 | /// </summary> | ||
| 469 | /// <param name="node">Element to parse</param> | ||
| 470 | /// <param name="fileSystemSafeBundleName"></param> | ||
| 471 | private string ParseLogElement(XElement node, string fileSystemSafeBundleName) | ||
| 472 | { | ||
| 473 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 474 | var disableLog = YesNoType.NotSet; | ||
| 475 | var variable = "WixBundleLog"; | ||
| 476 | var logPrefix = fileSystemSafeBundleName ?? "Setup"; | ||
| 477 | var logExtension = ".log"; | ||
| 478 | |||
| 479 | foreach (var attrib in node.Attributes()) | ||
| 480 | { | ||
| 481 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 482 | { | ||
| 483 | switch (attrib.Name.LocalName) | ||
| 484 | { | ||
| 485 | case "Disable": | ||
| 486 | disableLog = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 487 | break; | ||
| 488 | case "PathVariable": | ||
| 489 | variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 490 | break; | ||
| 491 | case "Prefix": | ||
| 492 | logPrefix = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 493 | break; | ||
| 494 | case "Extension": | ||
| 495 | logExtension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 496 | break; | ||
| 497 | default: | ||
| 498 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 499 | break; | ||
| 500 | } | ||
| 501 | } | ||
| 502 | else | ||
| 503 | { | ||
| 504 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 505 | } | ||
| 506 | } | ||
| 507 | |||
| 508 | if (!logExtension.StartsWith(".", StringComparison.Ordinal)) | ||
| 509 | { | ||
| 510 | logExtension = String.Concat(".", logExtension); | ||
| 511 | } | ||
| 512 | |||
| 513 | this.Core.ParseForExtensionElements(node); | ||
| 514 | |||
| 515 | return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension); | ||
| 516 | } | ||
| 517 | |||
| 518 | /// <summary> | ||
| 519 | /// Parse a Container element. | ||
| 520 | /// </summary> | ||
| 521 | /// <param name="node">Element to parse</param> | ||
| 522 | private void ParseContainerElement(XElement node) | ||
| 523 | { | ||
| 524 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 525 | Identifier id = null; | ||
| 526 | string downloadUrl = null; | ||
| 527 | string name = null; | ||
| 528 | var type = ContainerType.Detached; | ||
| 529 | |||
| 530 | foreach (var attrib in node.Attributes()) | ||
| 531 | { | ||
| 532 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 533 | { | ||
| 534 | switch (attrib.Name.LocalName) | ||
| 535 | { | ||
| 536 | case "Id": | ||
| 537 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 538 | if (id?.Id == BurnConstants.BurnUXContainerName || id?.Id == BurnConstants.BurnDefaultAttachedContainerName) | ||
| 539 | { | ||
| 540 | this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
| 541 | } | ||
| 542 | break; | ||
| 543 | case "DownloadUrl": | ||
| 544 | downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 545 | break; | ||
| 546 | case "Name": | ||
| 547 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 548 | break; | ||
| 549 | case "Type": | ||
| 550 | var typeString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 551 | switch (typeString) | ||
| 552 | { | ||
| 553 | case "attached": | ||
| 554 | type = ContainerType.Attached; | ||
| 555 | break; | ||
| 556 | case "detached": | ||
| 557 | type = ContainerType.Detached; | ||
| 558 | break; | ||
| 559 | default: | ||
| 560 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached")); | ||
| 561 | break; | ||
| 562 | } | ||
| 563 | break; | ||
| 564 | default: | ||
| 565 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 566 | break; | ||
| 567 | } | ||
| 568 | } | ||
| 569 | else | ||
| 570 | { | ||
| 571 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 572 | } | ||
| 573 | } | ||
| 574 | |||
| 575 | if (null == id) | ||
| 576 | { | ||
| 577 | if (!String.IsNullOrEmpty(name)) | ||
| 578 | { | ||
| 579 | id = this.Core.CreateIdentifierFromFilename(name); | ||
| 580 | } | ||
| 581 | |||
| 582 | if (null == id) | ||
| 583 | { | ||
| 584 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 585 | id = Identifier.Invalid; | ||
| 586 | } | ||
| 587 | else if (!Common.IsIdentifier(id.Id)) | ||
| 588 | { | ||
| 589 | this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
| 590 | } | ||
| 591 | } | ||
| 592 | else if (null == name) | ||
| 593 | { | ||
| 594 | name = id.Id; | ||
| 595 | } | ||
| 596 | |||
| 597 | if (!String.IsNullOrEmpty(downloadUrl) && ContainerType.Detached != type) | ||
| 598 | { | ||
| 599 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "Type", "attached")); | ||
| 600 | } | ||
| 601 | |||
| 602 | foreach (var child in node.Elements()) | ||
| 603 | { | ||
| 604 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 605 | { | ||
| 606 | switch (child.Name.LocalName) | ||
| 607 | { | ||
| 608 | case "PackageGroupRef": | ||
| 609 | this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.Container, id.Id); | ||
| 610 | break; | ||
| 611 | default: | ||
| 612 | this.Core.UnexpectedElement(node, child); | ||
| 613 | break; | ||
| 614 | } | ||
| 615 | } | ||
| 616 | else | ||
| 617 | { | ||
| 618 | this.Core.ParseExtensionElement(node, child); | ||
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | |||
| 623 | if (!this.Core.EncounteredError) | ||
| 624 | { | ||
| 625 | this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, id) | ||
| 626 | { | ||
| 627 | Name = name, | ||
| 628 | Type = type, | ||
| 629 | DownloadUrl = downloadUrl | ||
| 630 | }); | ||
| 631 | } | ||
| 632 | } | ||
| 633 | |||
| 634 | /// <summary> | ||
| 635 | /// Parse the BoostrapperApplication element. | ||
| 636 | /// </summary> | ||
| 637 | /// <param name="node">Element to parse</param> | ||
| 638 | private void ParseBootstrapperApplicationElement(XElement node) | ||
| 639 | { | ||
| 640 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 641 | Identifier id = null; | ||
| 642 | Identifier previousId = null; | ||
| 643 | var previousType = ComplexReferenceChildType.Unknown; | ||
| 644 | |||
| 645 | foreach (var attrib in node.Attributes()) | ||
| 646 | { | ||
| 647 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 648 | { | ||
| 649 | switch (attrib.Name.LocalName) | ||
| 650 | { | ||
| 651 | case "Id": | ||
| 652 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 653 | break; | ||
| 654 | default: | ||
| 655 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 656 | break; | ||
| 657 | } | ||
| 658 | } | ||
| 659 | } | ||
| 660 | |||
| 661 | foreach (var child in node.Elements()) | ||
| 662 | { | ||
| 663 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 664 | { | ||
| 665 | switch (child.Name.LocalName) | ||
| 666 | { | ||
| 667 | case "BootstrapperApplicationDll": | ||
| 668 | previousId = this.ParseBootstrapperApplicationDllElement(child, id, previousType, previousId); | ||
| 669 | previousType = ComplexReferenceChildType.Payload; | ||
| 670 | break; | ||
| 671 | case "Payload": | ||
| 672 | previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
| 673 | previousType = ComplexReferenceChildType.Payload; | ||
| 674 | break; | ||
| 675 | case "PayloadGroupRef": | ||
| 676 | previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
| 677 | previousType = ComplexReferenceChildType.PayloadGroup; | ||
| 678 | break; | ||
| 679 | default: | ||
| 680 | this.Core.UnexpectedElement(node, child); | ||
| 681 | break; | ||
| 682 | } | ||
| 683 | } | ||
| 684 | else | ||
| 685 | { | ||
| 686 | this.Core.ParseExtensionElement(node, child); | ||
| 687 | } | ||
| 688 | } | ||
| 689 | |||
| 690 | if (id != null) | ||
| 691 | { | ||
| 692 | this.Core.AddSymbol(new WixBootstrapperApplicationSymbol(sourceLineNumbers, id)); | ||
| 693 | } | ||
| 694 | } | ||
| 695 | |||
| 696 | /// <summary> | ||
| 697 | /// Parse the BoostrapperApplication element. | ||
| 698 | /// </summary> | ||
| 699 | /// <param name="node">Element to parse</param> | ||
| 700 | /// <param name="defaultId"></param> | ||
| 701 | /// <param name="previousType"></param> | ||
| 702 | /// <param name="previousId"></param> | ||
| 703 | private Identifier ParseBootstrapperApplicationDllElement(XElement node, Identifier defaultId, ComplexReferenceChildType previousType, Identifier previousId) | ||
| 704 | { | ||
| 705 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 706 | var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) | ||
| 707 | { | ||
| 708 | Id = defaultId, | ||
| 709 | }; | ||
| 710 | var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; | ||
| 711 | |||
| 712 | // This list lets us evaluate extension attributes *after* all core attributes | ||
| 713 | // have been parsed and dealt with, regardless of authoring order. | ||
| 714 | var extensionAttributes = new List<XAttribute>(); | ||
| 715 | |||
| 716 | foreach (var attrib in node.Attributes()) | ||
| 717 | { | ||
| 718 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 719 | { | ||
| 720 | switch (attrib.Name.LocalName) | ||
| 721 | { | ||
| 722 | case "Id": | ||
| 723 | compilerPayload.ParseId(attrib); | ||
| 724 | break; | ||
| 725 | case "Name": | ||
| 726 | compilerPayload.ParseName(attrib); | ||
| 727 | break; | ||
| 728 | case "SourceFile": | ||
| 729 | compilerPayload.ParseSourceFile(attrib); | ||
| 730 | break; | ||
| 731 | case "DpiAwareness": | ||
| 732 | var dpiAwarenessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 733 | switch (dpiAwarenessValue) | ||
| 734 | { | ||
| 735 | case "gdiScaled": | ||
| 736 | dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.GdiScaled; | ||
| 737 | break; | ||
| 738 | case "perMonitor": | ||
| 739 | dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitor; | ||
| 740 | break; | ||
| 741 | case "perMonitorV2": | ||
| 742 | dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; | ||
| 743 | break; | ||
| 744 | case "system": | ||
| 745 | dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.System; | ||
| 746 | break; | ||
| 747 | case "unaware": | ||
| 748 | dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.Unaware; | ||
| 749 | break; | ||
| 750 | default: | ||
| 751 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "DpiAwareness", dpiAwarenessValue, "gdiScaled", "perMonitor", "perMonitorV2", "system", "unaware")); | ||
| 752 | break; | ||
| 753 | } | ||
| 754 | break; | ||
| 755 | default: | ||
| 756 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 757 | break; | ||
| 758 | } | ||
| 759 | } | ||
| 760 | else | ||
| 761 | { | ||
| 762 | extensionAttributes.Add(attrib); | ||
| 763 | } | ||
| 764 | } | ||
| 765 | |||
| 766 | compilerPayload.FinishCompilingPayload(); | ||
| 767 | |||
| 768 | // Now that the Id is known, we can parse the extension attributes. | ||
| 769 | var context = new Dictionary<string, string> | ||
| 770 | { | ||
| 771 | ["Id"] = compilerPayload.Id.Id, | ||
| 772 | }; | ||
| 773 | |||
| 774 | foreach (var extensionAttribute in extensionAttributes) | ||
| 775 | { | ||
| 776 | this.Core.ParseExtensionAttribute(node, extensionAttribute, context); | ||
| 777 | } | ||
| 778 | |||
| 779 | foreach (var child in node.Elements()) | ||
| 780 | { | ||
| 781 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 782 | { | ||
| 783 | switch (child.Name.LocalName) | ||
| 784 | { | ||
| 785 | default: | ||
| 786 | this.Core.UnexpectedElement(node, child); | ||
| 787 | break; | ||
| 788 | } | ||
| 789 | } | ||
| 790 | else | ||
| 791 | { | ||
| 792 | this.Core.ParseExtensionElement(node, child); | ||
| 793 | } | ||
| 794 | } | ||
| 795 | |||
| 796 | if (!this.Core.EncounteredError) | ||
| 797 | { | ||
| 798 | compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); | ||
| 799 | this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnUXContainerId) | ||
| 800 | { | ||
| 801 | Name = "bundle-ux.cab", | ||
| 802 | Type = ContainerType.Attached | ||
| 803 | }); | ||
| 804 | |||
| 805 | this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, compilerPayload.Id) | ||
| 806 | { | ||
| 807 | DpiAwareness = dpiAwareness, | ||
| 808 | }); | ||
| 809 | } | ||
| 810 | |||
| 811 | return compilerPayload.Id; | ||
| 812 | } | ||
| 813 | |||
| 814 | /// <summary> | ||
| 815 | /// Parse the BoostrapperApplicationRef element. | ||
| 816 | /// </summary> | ||
| 817 | /// <param name="node">Element to parse</param> | ||
| 818 | private void ParseBootstrapperApplicationRefElement(XElement node) | ||
| 819 | { | ||
| 820 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 821 | string id = null; | ||
| 822 | Identifier previousId = null; | ||
| 823 | var previousType = ComplexReferenceChildType.Unknown; | ||
| 824 | |||
| 825 | foreach (var attrib in node.Attributes()) | ||
| 826 | { | ||
| 827 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 828 | { | ||
| 829 | switch (attrib.Name.LocalName) | ||
| 830 | { | ||
| 831 | case "Id": | ||
| 832 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 833 | break; | ||
| 834 | default: | ||
| 835 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 836 | break; | ||
| 837 | } | ||
| 838 | } | ||
| 839 | else | ||
| 840 | { | ||
| 841 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 842 | } | ||
| 843 | } | ||
| 844 | |||
| 845 | foreach (var child in node.Elements()) | ||
| 846 | { | ||
| 847 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 848 | { | ||
| 849 | switch (child.Name.LocalName) | ||
| 850 | { | ||
| 851 | case "Payload": | ||
| 852 | previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
| 853 | previousType = ComplexReferenceChildType.Payload; | ||
| 854 | break; | ||
| 855 | case "PayloadGroupRef": | ||
| 856 | previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
| 857 | previousType = ComplexReferenceChildType.PayloadGroup; | ||
| 858 | break; | ||
| 859 | default: | ||
| 860 | this.Core.UnexpectedElement(node, child); | ||
| 861 | break; | ||
| 862 | } | ||
| 863 | } | ||
| 864 | else | ||
| 865 | { | ||
| 866 | this.Core.ParseExtensionElement(node, child); | ||
| 867 | } | ||
| 868 | } | ||
| 869 | |||
| 870 | |||
| 871 | if (String.IsNullOrEmpty(id)) | ||
| 872 | { | ||
| 873 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 874 | } | ||
| 875 | else | ||
| 876 | { | ||
| 877 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBootstrapperApplication, id); | ||
| 878 | } | ||
| 879 | } | ||
| 880 | |||
| 881 | |||
| 882 | |||
| 883 | /// <summary> | ||
| 884 | /// Parses a BundleCustomData element. | ||
| 885 | /// </summary> | ||
| 886 | /// <param name="node">Element to parse.</param> | ||
| 887 | private void ParseBundleCustomDataElement(XElement node) | ||
| 888 | { | ||
| 889 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 890 | string customDataId = null; | ||
| 891 | WixBundleCustomDataType? customDataType = null; | ||
| 892 | string extensionId = null; | ||
| 893 | var attributeDefinitions = new List<WixBundleCustomDataAttributeSymbol>(); | ||
| 894 | var foundAttributeDefinitions = false; | ||
| 895 | |||
| 896 | foreach (var attrib in node.Attributes()) | ||
| 897 | { | ||
| 898 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 899 | { | ||
| 900 | switch (attrib.Name.LocalName) | ||
| 901 | { | ||
| 902 | case "Id": | ||
| 903 | customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 904 | break; | ||
| 905 | case "Type": | ||
| 906 | var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 907 | switch (typeValue) | ||
| 908 | { | ||
| 909 | case "bootstrapperApplication": | ||
| 910 | customDataType = WixBundleCustomDataType.BootstrapperApplication; | ||
| 911 | break; | ||
| 912 | case "bundleExtension": | ||
| 913 | customDataType = WixBundleCustomDataType.BundleExtension; | ||
| 914 | break; | ||
| 915 | default: | ||
| 916 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "bootstrapperApplication", "bundleExtension")); | ||
| 917 | customDataType = WixBundleCustomDataType.Unknown; // set a value to prevent expected attribute error below. | ||
| 918 | break; | ||
| 919 | } | ||
| 920 | break; | ||
| 921 | case "ExtensionId": | ||
| 922 | extensionId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 923 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleExtension, extensionId); | ||
| 924 | break; | ||
| 925 | default: | ||
| 926 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 927 | break; | ||
| 928 | } | ||
| 929 | } | ||
| 930 | else | ||
| 931 | { | ||
| 932 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 933 | } | ||
| 934 | } | ||
| 935 | |||
| 936 | if (null == customDataId) | ||
| 937 | { | ||
| 938 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 939 | } | ||
| 940 | |||
| 941 | var hasExtensionId = null != extensionId; | ||
| 942 | if (!customDataType.HasValue) | ||
| 943 | { | ||
| 944 | customDataType = hasExtensionId ? WixBundleCustomDataType.BundleExtension : WixBundleCustomDataType.BootstrapperApplication; | ||
| 945 | } | ||
| 946 | |||
| 947 | if (!customDataType.HasValue) | ||
| 948 | { | ||
| 949 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); | ||
| 950 | } | ||
| 951 | else if (hasExtensionId) | ||
| 952 | { | ||
| 953 | if (customDataType.Value == WixBundleCustomDataType.BootstrapperApplication) | ||
| 954 | { | ||
| 955 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensonId", "Type", "bootstrapperApplication")); | ||
| 956 | } | ||
| 957 | } | ||
| 958 | else if (customDataType.Value == WixBundleCustomDataType.BundleExtension) | ||
| 959 | { | ||
| 960 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensionId", "Type", "bundleExtension")); | ||
| 961 | } | ||
| 962 | |||
| 963 | foreach (var child in node.Elements()) | ||
| 964 | { | ||
| 965 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 966 | { | ||
| 967 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 968 | switch (child.Name.LocalName) | ||
| 969 | { | ||
| 970 | case "BundleAttributeDefinition": | ||
| 971 | foundAttributeDefinitions = true; | ||
| 972 | |||
| 973 | var attributeDefinition = this.ParseBundleAttributeDefinitionElement(child, childSourceLineNumbers, customDataId); | ||
| 974 | if (attributeDefinition != null) | ||
| 975 | { | ||
| 976 | attributeDefinitions.Add(attributeDefinition); | ||
| 977 | } | ||
| 978 | break; | ||
| 979 | case "BundleElement": | ||
| 980 | this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); | ||
| 981 | break; | ||
| 982 | default: | ||
| 983 | this.Core.UnexpectedElement(node, child); | ||
| 984 | break; | ||
| 985 | } | ||
| 986 | } | ||
| 987 | else | ||
| 988 | { | ||
| 989 | this.Core.ParseExtensionElement(node, child); | ||
| 990 | } | ||
| 991 | } | ||
| 992 | |||
| 993 | if (attributeDefinitions.Count > 0) | ||
| 994 | { | ||
| 995 | if (!this.Core.EncounteredError) | ||
| 996 | { | ||
| 997 | var attributeNames = String.Join(new string(WixBundleCustomDataSymbol.AttributeNamesSeparator, 1), attributeDefinitions.Select(c => c.Name)); | ||
| 998 | |||
| 999 | this.Core.AddSymbol(new WixBundleCustomDataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, customDataId)) | ||
| 1000 | { | ||
| 1001 | AttributeNames = attributeNames, | ||
| 1002 | Type = customDataType.Value, | ||
| 1003 | BundleExtensionRef = extensionId, | ||
| 1004 | }); | ||
| 1005 | } | ||
| 1006 | } | ||
| 1007 | else if (!foundAttributeDefinitions) | ||
| 1008 | { | ||
| 1009 | this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "BundleAttributeDefinition")); | ||
| 1010 | } | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | /// <summary> | ||
| 1014 | /// Parses a BundleCustomDataRef element. | ||
| 1015 | /// </summary> | ||
| 1016 | /// <param name="node">Element to parse.</param> | ||
| 1017 | private void ParseBundleCustomDataRefElement(XElement node) | ||
| 1018 | { | ||
| 1019 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1020 | string customDataId = null; | ||
| 1021 | |||
| 1022 | foreach (var attrib in node.Attributes()) | ||
| 1023 | { | ||
| 1024 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1025 | { | ||
| 1026 | switch (attrib.Name.LocalName) | ||
| 1027 | { | ||
| 1028 | case "Id": | ||
| 1029 | customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 1030 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); | ||
| 1031 | break; | ||
| 1032 | default: | ||
| 1033 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1034 | break; | ||
| 1035 | } | ||
| 1036 | } | ||
| 1037 | else | ||
| 1038 | { | ||
| 1039 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1040 | } | ||
| 1041 | } | ||
| 1042 | |||
| 1043 | if (null == customDataId) | ||
| 1044 | { | ||
| 1045 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 1046 | } | ||
| 1047 | |||
| 1048 | foreach (var child in node.Elements()) | ||
| 1049 | { | ||
| 1050 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1051 | { | ||
| 1052 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 1053 | switch (child.Name.LocalName) | ||
| 1054 | { | ||
| 1055 | case "BundleElement": | ||
| 1056 | this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); | ||
| 1057 | break; | ||
| 1058 | default: | ||
| 1059 | this.Core.UnexpectedElement(node, child); | ||
| 1060 | break; | ||
| 1061 | } | ||
| 1062 | } | ||
| 1063 | else | ||
| 1064 | { | ||
| 1065 | this.Core.ParseExtensionElement(node, child); | ||
| 1066 | } | ||
| 1067 | } | ||
| 1068 | } | ||
| 1069 | |||
| 1070 | /// <summary> | ||
| 1071 | /// Parses a BundleAttributeDefinition element. | ||
| 1072 | /// </summary> | ||
| 1073 | /// <param name="node">Element to parse.</param> | ||
| 1074 | /// <param name="sourceLineNumbers">Element's SourceLineNumbers.</param> | ||
| 1075 | /// <param name="customDataId">BundleCustomData Id.</param> | ||
| 1076 | private WixBundleCustomDataAttributeSymbol ParseBundleAttributeDefinitionElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) | ||
| 1077 | { | ||
| 1078 | string attributeName = null; | ||
| 1079 | |||
| 1080 | foreach (var attrib in node.Attributes()) | ||
| 1081 | { | ||
| 1082 | switch (attrib.Name.LocalName) | ||
| 1083 | { | ||
| 1084 | case "Id": | ||
| 1085 | attributeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1086 | break; | ||
| 1087 | default: | ||
| 1088 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1089 | break; | ||
| 1090 | } | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | if (null == attributeName) | ||
| 1094 | { | ||
| 1095 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | this.Core.ParseForExtensionElements(node); | ||
| 1099 | |||
| 1100 | if (this.Core.EncounteredError) | ||
| 1101 | { | ||
| 1102 | return null; | ||
| 1103 | } | ||
| 1104 | |||
| 1105 | var customDataAttribute = this.Core.AddSymbol(new WixBundleCustomDataAttributeSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, attributeName)) | ||
| 1106 | { | ||
| 1107 | CustomDataRef = customDataId, | ||
| 1108 | Name = attributeName, | ||
| 1109 | }); | ||
| 1110 | return customDataAttribute; | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | /// <summary> | ||
| 1114 | /// Parses a BundleElement element. | ||
| 1115 | /// </summary> | ||
| 1116 | /// <param name="node">Element to parse.</param> | ||
| 1117 | /// <param name="sourceLineNumbers">Element's SourceLineNumbers.</param> | ||
| 1118 | /// <param name="customDataId">BundleCustomData Id.</param> | ||
| 1119 | private void ParseBundleElementElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) | ||
| 1120 | { | ||
| 1121 | var elementId = Guid.NewGuid().ToString("N").ToUpperInvariant(); | ||
| 1122 | |||
| 1123 | foreach (var attrib in node.Attributes()) | ||
| 1124 | { | ||
| 1125 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1126 | } | ||
| 1127 | |||
| 1128 | foreach (var child in node.Elements()) | ||
| 1129 | { | ||
| 1130 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 1131 | switch (child.Name.LocalName) | ||
| 1132 | { | ||
| 1133 | case "BundleAttribute": | ||
| 1134 | string attributeName = null; | ||
| 1135 | string value = null; | ||
| 1136 | foreach (var attrib in child.Attributes()) | ||
| 1137 | { | ||
| 1138 | switch (attrib.Name.LocalName) | ||
| 1139 | { | ||
| 1140 | case "Id": | ||
| 1141 | attributeName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); | ||
| 1142 | break; | ||
| 1143 | case "Value": | ||
| 1144 | value = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); | ||
| 1145 | break; | ||
| 1146 | default: | ||
| 1147 | this.Core.ParseExtensionAttribute(child, attrib); | ||
| 1148 | break; | ||
| 1149 | } | ||
| 1150 | } | ||
| 1151 | |||
| 1152 | if (null == attributeName) | ||
| 1153 | { | ||
| 1154 | this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); | ||
| 1155 | } | ||
| 1156 | |||
| 1157 | if (!this.Core.EncounteredError) | ||
| 1158 | { | ||
| 1159 | this.Core.AddSymbol(new WixBundleCustomDataCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, elementId, attributeName)) | ||
| 1160 | { | ||
| 1161 | ElementId = elementId, | ||
| 1162 | AttributeRef = attributeName, | ||
| 1163 | CustomDataRef = customDataId, | ||
| 1164 | Value = value, | ||
| 1165 | }); | ||
| 1166 | } | ||
| 1167 | break; | ||
| 1168 | default: | ||
| 1169 | this.Core.UnexpectedElement(node, child); | ||
| 1170 | break; | ||
| 1171 | } | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | if (!this.Core.EncounteredError) | ||
| 1175 | { | ||
| 1176 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); | ||
| 1177 | } | ||
| 1178 | } | ||
| 1179 | |||
| 1180 | /// <summary> | ||
| 1181 | /// Parse the BundleExtension element. | ||
| 1182 | /// </summary> | ||
| 1183 | /// <param name="node">Element to parse</param> | ||
| 1184 | private void ParseBundleExtensionElement(XElement node) | ||
| 1185 | { | ||
| 1186 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1187 | var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); | ||
| 1188 | Identifier previousId = null; | ||
| 1189 | var previousType = ComplexReferenceChildType.Unknown; | ||
| 1190 | |||
| 1191 | // This list lets us evaluate extension attributes *after* all core attributes | ||
| 1192 | // have been parsed and dealt with, regardless of authoring order. | ||
| 1193 | var extensionAttributes = new List<XAttribute>(); | ||
| 1194 | |||
| 1195 | foreach (var attrib in node.Attributes()) | ||
| 1196 | { | ||
| 1197 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1198 | { | ||
| 1199 | switch (attrib.Name.LocalName) | ||
| 1200 | { | ||
| 1201 | case "Id": | ||
| 1202 | compilerPayload.ParseId(attrib); | ||
| 1203 | break; | ||
| 1204 | case "Name": | ||
| 1205 | compilerPayload.ParseName(attrib); | ||
| 1206 | break; | ||
| 1207 | case "SourceFile": | ||
| 1208 | compilerPayload.ParseSourceFile(attrib); | ||
| 1209 | break; | ||
| 1210 | default: | ||
| 1211 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1212 | break; | ||
| 1213 | } | ||
| 1214 | } | ||
| 1215 | else | ||
| 1216 | { | ||
| 1217 | extensionAttributes.Add(attrib); | ||
| 1218 | } | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | compilerPayload.FinishCompilingPayload(); | ||
| 1222 | |||
| 1223 | // Now that the Id is known, we can parse the extension attributes. | ||
| 1224 | var context = new Dictionary<string, string> | ||
| 1225 | { | ||
| 1226 | ["Id"] = compilerPayload.Id.Id, | ||
| 1227 | }; | ||
| 1228 | |||
| 1229 | foreach (var extensionAttribute in extensionAttributes) | ||
| 1230 | { | ||
| 1231 | this.Core.ParseExtensionAttribute(node, extensionAttribute, context); | ||
| 1232 | } | ||
| 1233 | |||
| 1234 | compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); | ||
| 1235 | previousId = compilerPayload.Id; | ||
| 1236 | previousType = ComplexReferenceChildType.Payload; | ||
| 1237 | |||
| 1238 | foreach (var child in node.Elements()) | ||
| 1239 | { | ||
| 1240 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1241 | { | ||
| 1242 | switch (child.Name.LocalName) | ||
| 1243 | { | ||
| 1244 | case "Payload": | ||
| 1245 | previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
| 1246 | previousType = ComplexReferenceChildType.Payload; | ||
| 1247 | break; | ||
| 1248 | case "PayloadGroupRef": | ||
| 1249 | previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); | ||
| 1250 | previousType = ComplexReferenceChildType.PayloadGroup; | ||
| 1251 | break; | ||
| 1252 | default: | ||
| 1253 | this.Core.UnexpectedElement(node, child); | ||
| 1254 | break; | ||
| 1255 | } | ||
| 1256 | } | ||
| 1257 | else | ||
| 1258 | { | ||
| 1259 | this.Core.ParseExtensionElement(node, child); | ||
| 1260 | } | ||
| 1261 | } | ||
| 1262 | |||
| 1263 | // Add the BundleExtension. | ||
| 1264 | if (!this.Core.EncounteredError) | ||
| 1265 | { | ||
| 1266 | this.Core.AddSymbol(new WixBundleExtensionSymbol(sourceLineNumbers, compilerPayload.Id) | ||
| 1267 | { | ||
| 1268 | PayloadRef = compilerPayload.Id.Id, | ||
| 1269 | }); | ||
| 1270 | } | ||
| 1271 | } | ||
| 1272 | |||
| 1273 | /// <summary> | ||
| 1274 | /// Parse the OptionalUpdateRegistration element. | ||
| 1275 | /// </summary> | ||
| 1276 | /// <param name="node">The element to parse.</param> | ||
| 1277 | /// <param name="defaultManufacturer">The manufacturer.</param> | ||
| 1278 | /// <param name="defaultProductFamily">The product family.</param> | ||
| 1279 | /// <param name="defaultName">The bundle name.</param> | ||
| 1280 | private void ParseOptionalUpdateRegistrationElement(XElement node, string defaultManufacturer, string defaultProductFamily, string defaultName) | ||
| 1281 | { | ||
| 1282 | const string defaultClassification = "Update"; | ||
| 1283 | |||
| 1284 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1285 | string manufacturer = null; | ||
| 1286 | string department = null; | ||
| 1287 | string productFamily = null; | ||
| 1288 | string name = null; | ||
| 1289 | var classification = defaultClassification; | ||
| 1290 | |||
| 1291 | foreach (var attrib in node.Attributes()) | ||
| 1292 | { | ||
| 1293 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1294 | { | ||
| 1295 | switch (attrib.Name.LocalName) | ||
| 1296 | { | ||
| 1297 | case "Manufacturer": | ||
| 1298 | manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1299 | break; | ||
| 1300 | case "Department": | ||
| 1301 | department = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1302 | break; | ||
| 1303 | case "ProductFamily": | ||
| 1304 | productFamily = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1305 | break; | ||
| 1306 | case "Name": | ||
| 1307 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1308 | break; | ||
| 1309 | case "Classification": | ||
| 1310 | classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1311 | break; | ||
| 1312 | default: | ||
| 1313 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1314 | break; | ||
| 1315 | } | ||
| 1316 | } | ||
| 1317 | else | ||
| 1318 | { | ||
| 1319 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1320 | } | ||
| 1321 | } | ||
| 1322 | |||
| 1323 | if (String.IsNullOrEmpty(manufacturer)) | ||
| 1324 | { | ||
| 1325 | if (!String.IsNullOrEmpty(defaultManufacturer)) | ||
| 1326 | { | ||
| 1327 | manufacturer = defaultManufacturer; | ||
| 1328 | } | ||
| 1329 | else | ||
| 1330 | { | ||
| 1331 | this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Manufacturer", node.Parent.Name.LocalName)); | ||
| 1332 | } | ||
| 1333 | } | ||
| 1334 | |||
| 1335 | if (String.IsNullOrEmpty(productFamily)) | ||
| 1336 | { | ||
| 1337 | if (!String.IsNullOrEmpty(defaultProductFamily)) | ||
| 1338 | { | ||
| 1339 | productFamily = defaultProductFamily; | ||
| 1340 | } | ||
| 1341 | } | ||
| 1342 | |||
| 1343 | if (String.IsNullOrEmpty(name)) | ||
| 1344 | { | ||
| 1345 | if (!String.IsNullOrEmpty(defaultName)) | ||
| 1346 | { | ||
| 1347 | name = defaultName; | ||
| 1348 | } | ||
| 1349 | else | ||
| 1350 | { | ||
| 1351 | this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Name", node.Parent.Name.LocalName)); | ||
| 1352 | } | ||
| 1353 | } | ||
| 1354 | |||
| 1355 | if (String.IsNullOrEmpty(classification)) | ||
| 1356 | { | ||
| 1357 | this.Core.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, node.Name.LocalName, "Classification", defaultClassification)); | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | this.Core.ParseForExtensionElements(node); | ||
| 1361 | |||
| 1362 | if (!this.Core.EncounteredError) | ||
| 1363 | { | ||
| 1364 | this.Core.AddSymbol(new WixUpdateRegistrationSymbol(sourceLineNumbers) | ||
| 1365 | { | ||
| 1366 | Manufacturer = manufacturer, | ||
| 1367 | Department = department, | ||
| 1368 | ProductFamily = productFamily, | ||
| 1369 | Name = name, | ||
| 1370 | Classification = classification | ||
| 1371 | }); | ||
| 1372 | } | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | /// <summary> | ||
| 1376 | /// Parse Payload element. | ||
| 1377 | /// </summary> | ||
| 1378 | /// <param name="node">Element to parse</param> | ||
| 1379 | /// <param name="parentType">ComplexReferenceParentType of parent element. (BA or PayloadGroup)</param> | ||
| 1380 | /// <param name="parentId">Identifier of parent element.</param> | ||
| 1381 | /// <param name="previousType"></param> | ||
| 1382 | /// <param name="previousId"></param> | ||
| 1383 | private Identifier ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) | ||
| 1384 | { | ||
| 1385 | Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); | ||
| 1386 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); | ||
| 1387 | |||
| 1388 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1389 | var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); | ||
| 1390 | |||
| 1391 | // This list lets us evaluate extension attributes *after* all core attributes | ||
| 1392 | // have been parsed and dealt with, regardless of authoring order. | ||
| 1393 | var extensionAttributes = new List<XAttribute>(); | ||
| 1394 | |||
| 1395 | foreach (var attrib in node.Attributes()) | ||
| 1396 | { | ||
| 1397 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1398 | { | ||
| 1399 | var allowed = true; | ||
| 1400 | switch (attrib.Name.LocalName) | ||
| 1401 | { | ||
| 1402 | case "Id": | ||
| 1403 | compilerPayload.ParseId(attrib); | ||
| 1404 | break; | ||
| 1405 | case "Compressed": | ||
| 1406 | compilerPayload.ParseCompressed(attrib); | ||
| 1407 | break; | ||
| 1408 | case "Name": | ||
| 1409 | compilerPayload.ParseName(attrib); | ||
| 1410 | break; | ||
| 1411 | case "SourceFile": | ||
| 1412 | compilerPayload.ParseSourceFile(attrib); | ||
| 1413 | break; | ||
| 1414 | case "DownloadUrl": | ||
| 1415 | compilerPayload.ParseDownloadUrl(attrib); | ||
| 1416 | break; | ||
| 1417 | default: | ||
| 1418 | allowed = false; | ||
| 1419 | break; | ||
| 1420 | } | ||
| 1421 | |||
| 1422 | if (!allowed) | ||
| 1423 | { | ||
| 1424 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1425 | } | ||
| 1426 | } | ||
| 1427 | else | ||
| 1428 | { | ||
| 1429 | extensionAttributes.Add(attrib); | ||
| 1430 | } | ||
| 1431 | } | ||
| 1432 | |||
| 1433 | compilerPayload.FinishCompilingPayload(); | ||
| 1434 | |||
| 1435 | // Now that the PayloadId is known, we can parse the extension attributes. | ||
| 1436 | var context = new Dictionary<string, string> | ||
| 1437 | { | ||
| 1438 | ["Id"] = compilerPayload.Id.Id, | ||
| 1439 | }; | ||
| 1440 | |||
| 1441 | foreach (var extensionAttribute in extensionAttributes) | ||
| 1442 | { | ||
| 1443 | this.Core.ParseExtensionAttribute(node, extensionAttribute, context); | ||
| 1444 | } | ||
| 1445 | |||
| 1446 | foreach (var child in node.Elements()) | ||
| 1447 | { | ||
| 1448 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1449 | { | ||
| 1450 | switch (child.Name.LocalName) | ||
| 1451 | { | ||
| 1452 | default: | ||
| 1453 | this.Core.UnexpectedElement(node, child); | ||
| 1454 | break; | ||
| 1455 | } | ||
| 1456 | } | ||
| 1457 | else | ||
| 1458 | { | ||
| 1459 | this.Core.ParseExtensionElement(node, child, context); | ||
| 1460 | } | ||
| 1461 | } | ||
| 1462 | |||
| 1463 | compilerPayload.CreatePayloadSymbol(parentType, parentId?.Id, previousType, previousId?.Id); | ||
| 1464 | |||
| 1465 | return compilerPayload.Id; | ||
| 1466 | } | ||
| 1467 | |||
| 1468 | /// <summary> | ||
| 1469 | /// Parse PayloadGroup element. | ||
| 1470 | /// </summary> | ||
| 1471 | /// <param name="node">Element to parse</param> | ||
| 1472 | /// <param name="parentType">Optional ComplexReferenceParentType of parent element. (typically another PayloadGroup)</param> | ||
| 1473 | /// <param name="parentId">Identifier of parent element.</param> | ||
| 1474 | private void ParsePayloadGroupElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId) | ||
| 1475 | { | ||
| 1476 | Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType); | ||
| 1477 | |||
| 1478 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1479 | Identifier id = null; | ||
| 1480 | |||
| 1481 | foreach (var attrib in node.Attributes()) | ||
| 1482 | { | ||
| 1483 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1484 | { | ||
| 1485 | switch (attrib.Name.LocalName) | ||
| 1486 | { | ||
| 1487 | case "Id": | ||
| 1488 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1489 | break; | ||
| 1490 | default: | ||
| 1491 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1492 | break; | ||
| 1493 | } | ||
| 1494 | } | ||
| 1495 | else | ||
| 1496 | { | ||
| 1497 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1498 | } | ||
| 1499 | } | ||
| 1500 | |||
| 1501 | if (null == id) | ||
| 1502 | { | ||
| 1503 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 1504 | id = Identifier.Invalid; | ||
| 1505 | } | ||
| 1506 | |||
| 1507 | var previousType = ComplexReferenceChildType.Unknown; | ||
| 1508 | Identifier previousId = null; | ||
| 1509 | foreach (var child in node.Elements()) | ||
| 1510 | { | ||
| 1511 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1512 | { | ||
| 1513 | WixBundlePackageType? packageType = null; | ||
| 1514 | switch (child.Name.LocalName) | ||
| 1515 | { | ||
| 1516 | case "ExePackagePayload": | ||
| 1517 | packageType = WixBundlePackageType.Exe; | ||
| 1518 | break; | ||
| 1519 | case "MsiPackagePayload": | ||
| 1520 | packageType = WixBundlePackageType.Msi; | ||
| 1521 | break; | ||
| 1522 | case "MspPackagePayload": | ||
| 1523 | packageType = WixBundlePackageType.Msp; | ||
| 1524 | break; | ||
| 1525 | case "MsuPackagePayload": | ||
| 1526 | packageType = WixBundlePackageType.Msu; | ||
| 1527 | break; | ||
| 1528 | case "Payload": | ||
| 1529 | previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); | ||
| 1530 | previousType = ComplexReferenceChildType.Payload; | ||
| 1531 | break; | ||
| 1532 | case "PayloadGroupRef": | ||
| 1533 | previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); | ||
| 1534 | previousType = ComplexReferenceChildType.PayloadGroup; | ||
| 1535 | break; | ||
| 1536 | default: | ||
| 1537 | this.Core.UnexpectedElement(node, child); | ||
| 1538 | break; | ||
| 1539 | } | ||
| 1540 | |||
| 1541 | if (packageType.HasValue) | ||
| 1542 | { | ||
| 1543 | var compilerPayload = this.ParsePackagePayloadElement(null, child, packageType.Value, null); | ||
| 1544 | var payloadSymbol = compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.PayloadGroup, id?.Id, previousType, previousId?.Id); | ||
| 1545 | if (payloadSymbol != null) | ||
| 1546 | { | ||
| 1547 | previousId = payloadSymbol.Id; | ||
| 1548 | previousType = ComplexReferenceChildType.Payload; | ||
| 1549 | |||
| 1550 | this.CreatePackagePayloadSymbol(payloadSymbol.SourceLineNumbers, packageType.Value, payloadSymbol.Id, ComplexReferenceParentType.PayloadGroup, id); | ||
| 1551 | } | ||
| 1552 | } | ||
| 1553 | } | ||
| 1554 | else | ||
| 1555 | { | ||
| 1556 | this.Core.ParseExtensionElement(node, child); | ||
| 1557 | } | ||
| 1558 | } | ||
| 1559 | |||
| 1560 | |||
| 1561 | if (!this.Core.EncounteredError) | ||
| 1562 | { | ||
| 1563 | this.Core.AddSymbol(new WixBundlePayloadGroupSymbol(sourceLineNumbers, id)); | ||
| 1564 | |||
| 1565 | this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null); | ||
| 1566 | } | ||
| 1567 | } | ||
| 1568 | |||
| 1569 | /// <summary> | ||
| 1570 | /// Parses a payload group reference element. | ||
| 1571 | /// </summary> | ||
| 1572 | /// <param name="node">Element to parse.</param> | ||
| 1573 | /// <param name="parentType">ComplexReferenceParentType of parent element (BA or PayloadGroup).</param> | ||
| 1574 | /// <param name="parentId">Identifier of parent element.</param> | ||
| 1575 | /// <param name="previousType"></param> | ||
| 1576 | /// <param name="previousId"></param> | ||
| 1577 | private Identifier ParsePayloadGroupRefElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) | ||
| 1578 | { | ||
| 1579 | Debug.Assert(ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); | ||
| 1580 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); | ||
| 1581 | |||
| 1582 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1583 | Identifier id = null; | ||
| 1584 | |||
| 1585 | foreach (var attrib in node.Attributes()) | ||
| 1586 | { | ||
| 1587 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1588 | { | ||
| 1589 | switch (attrib.Name.LocalName) | ||
| 1590 | { | ||
| 1591 | case "Id": | ||
| 1592 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1593 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePayloadGroup, id.Id); | ||
| 1594 | break; | ||
| 1595 | default: | ||
| 1596 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1597 | break; | ||
| 1598 | } | ||
| 1599 | } | ||
| 1600 | else | ||
| 1601 | { | ||
| 1602 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1603 | } | ||
| 1604 | } | ||
| 1605 | |||
| 1606 | if (null == id) | ||
| 1607 | { | ||
| 1608 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 1609 | } | ||
| 1610 | |||
| 1611 | this.Core.ParseForExtensionElements(node); | ||
| 1612 | |||
| 1613 | this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id); | ||
| 1614 | |||
| 1615 | return id; | ||
| 1616 | } | ||
| 1617 | |||
| 1618 | /// <summary> | ||
| 1619 | /// Parse ExitCode element. | ||
| 1620 | /// </summary> | ||
| 1621 | /// <param name="node">Element to parse</param> | ||
| 1622 | /// <param name="packageId">Id of parent element</param> | ||
| 1623 | private void ParseExitCodeElement(XElement node, string packageId) | ||
| 1624 | { | ||
| 1625 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1626 | var value = CompilerConstants.IntegerNotSet; | ||
| 1627 | var behavior = ExitCodeBehaviorType.NotSet; | ||
| 1628 | |||
| 1629 | foreach (var attrib in node.Attributes()) | ||
| 1630 | { | ||
| 1631 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1632 | { | ||
| 1633 | switch (attrib.Name.LocalName) | ||
| 1634 | { | ||
| 1635 | case "Value": | ||
| 1636 | value = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); | ||
| 1637 | break; | ||
| 1638 | case "Behavior": | ||
| 1639 | var behaviorString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1640 | switch (behaviorString) | ||
| 1641 | { | ||
| 1642 | case "error": | ||
| 1643 | behavior = ExitCodeBehaviorType.Error; | ||
| 1644 | break; | ||
| 1645 | case "forceReboot": | ||
| 1646 | behavior = ExitCodeBehaviorType.ForceReboot; | ||
| 1647 | break; | ||
| 1648 | case "scheduleReboot": | ||
| 1649 | behavior = ExitCodeBehaviorType.ScheduleReboot; | ||
| 1650 | break; | ||
| 1651 | case "success": | ||
| 1652 | behavior = ExitCodeBehaviorType.Success; | ||
| 1653 | break; | ||
| 1654 | default: | ||
| 1655 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Behavior", behaviorString, "success, error, scheduleReboot, forceReboot")); | ||
| 1656 | behavior = ExitCodeBehaviorType.Success; // set value to avoid ExpectedAttribute below. | ||
| 1657 | break; | ||
| 1658 | } | ||
| 1659 | break; | ||
| 1660 | default: | ||
| 1661 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1662 | break; | ||
| 1663 | } | ||
| 1664 | } | ||
| 1665 | else | ||
| 1666 | { | ||
| 1667 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1668 | } | ||
| 1669 | } | ||
| 1670 | |||
| 1671 | if (ExitCodeBehaviorType.NotSet == behavior) | ||
| 1672 | { | ||
| 1673 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Behavior")); | ||
| 1674 | } | ||
| 1675 | |||
| 1676 | this.Core.ParseForExtensionElements(node); | ||
| 1677 | |||
| 1678 | if (!this.Core.EncounteredError) | ||
| 1679 | { | ||
| 1680 | this.Core.AddSymbol(new WixBundlePackageExitCodeSymbol(sourceLineNumbers) | ||
| 1681 | { | ||
| 1682 | ChainPackageId = packageId, | ||
| 1683 | Code = value, | ||
| 1684 | Behavior = behavior | ||
| 1685 | }); | ||
| 1686 | } | ||
| 1687 | } | ||
| 1688 | |||
| 1689 | /// <summary> | ||
| 1690 | /// Parse Chain element. | ||
| 1691 | /// </summary> | ||
| 1692 | /// <param name="node">Element to parse</param> | ||
| 1693 | private void ParseChainElement(XElement node) | ||
| 1694 | { | ||
| 1695 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1696 | var attributes = WixChainAttributes.None; | ||
| 1697 | |||
| 1698 | foreach (var attrib in node.Attributes()) | ||
| 1699 | { | ||
| 1700 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1701 | { | ||
| 1702 | switch (attrib.Name.LocalName) | ||
| 1703 | { | ||
| 1704 | case "DisableRollback": | ||
| 1705 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 1706 | { | ||
| 1707 | attributes |= WixChainAttributes.DisableRollback; | ||
| 1708 | } | ||
| 1709 | break; | ||
| 1710 | case "DisableSystemRestore": | ||
| 1711 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 1712 | { | ||
| 1713 | attributes |= WixChainAttributes.DisableSystemRestore; | ||
| 1714 | } | ||
| 1715 | break; | ||
| 1716 | case "ParallelCache": | ||
| 1717 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 1718 | { | ||
| 1719 | attributes |= WixChainAttributes.ParallelCache; | ||
| 1720 | } | ||
| 1721 | break; | ||
| 1722 | default: | ||
| 1723 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1724 | break; | ||
| 1725 | } | ||
| 1726 | } | ||
| 1727 | else | ||
| 1728 | { | ||
| 1729 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1730 | } | ||
| 1731 | } | ||
| 1732 | |||
| 1733 | string previousId = null; | ||
| 1734 | var previousType = ComplexReferenceChildType.Unknown; | ||
| 1735 | |||
| 1736 | foreach (var child in node.Elements()) | ||
| 1737 | { | ||
| 1738 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1739 | { | ||
| 1740 | switch (child.Name.LocalName) | ||
| 1741 | { | ||
| 1742 | case "MsiPackage": | ||
| 1743 | previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); | ||
| 1744 | previousType = ComplexReferenceChildType.Package; | ||
| 1745 | break; | ||
| 1746 | case "MspPackage": | ||
| 1747 | previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); | ||
| 1748 | previousType = ComplexReferenceChildType.Package; | ||
| 1749 | break; | ||
| 1750 | case "MsuPackage": | ||
| 1751 | previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); | ||
| 1752 | previousType = ComplexReferenceChildType.Package; | ||
| 1753 | break; | ||
| 1754 | case "ExePackage": | ||
| 1755 | previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); | ||
| 1756 | previousType = ComplexReferenceChildType.Package; | ||
| 1757 | break; | ||
| 1758 | case "RollbackBoundary": | ||
| 1759 | previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); | ||
| 1760 | previousType = ComplexReferenceChildType.Package; | ||
| 1761 | break; | ||
| 1762 | case "PackageGroupRef": | ||
| 1763 | previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); | ||
| 1764 | previousType = ComplexReferenceChildType.PackageGroup; | ||
| 1765 | break; | ||
| 1766 | default: | ||
| 1767 | this.Core.UnexpectedElement(node, child); | ||
| 1768 | break; | ||
| 1769 | } | ||
| 1770 | } | ||
| 1771 | else | ||
| 1772 | { | ||
| 1773 | this.Core.ParseExtensionElement(node, child); | ||
| 1774 | } | ||
| 1775 | } | ||
| 1776 | |||
| 1777 | |||
| 1778 | if (null == previousId) | ||
| 1779 | { | ||
| 1780 | this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "MsiPackage", "ExePackage", "PackageGroupRef")); | ||
| 1781 | } | ||
| 1782 | |||
| 1783 | if (!this.Core.EncounteredError) | ||
| 1784 | { | ||
| 1785 | this.Core.AddSymbol(new WixChainSymbol(sourceLineNumbers) | ||
| 1786 | { | ||
| 1787 | Attributes = attributes | ||
| 1788 | }); | ||
| 1789 | } | ||
| 1790 | } | ||
| 1791 | |||
| 1792 | /// <summary> | ||
| 1793 | /// Parse MsiPackage element | ||
| 1794 | /// </summary> | ||
| 1795 | /// <param name="node">Element to parse</param> | ||
| 1796 | /// <param name="parentType">Type of parent group, if known.</param> | ||
| 1797 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
| 1798 | /// <param name="previousType">Type of previous item, if known.</param> | ||
| 1799 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
| 1800 | /// <returns>Identifier for package element.</returns> | ||
| 1801 | private string ParseMsiPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
| 1802 | { | ||
| 1803 | return this.ParseChainPackage(node, WixBundlePackageType.Msi, parentType, parentId, previousType, previousId); | ||
| 1804 | } | ||
| 1805 | |||
| 1806 | /// <summary> | ||
| 1807 | /// Parse MspPackage element | ||
| 1808 | /// </summary> | ||
| 1809 | /// <param name="node">Element to parse</param> | ||
| 1810 | /// <param name="parentType">Type of parent group, if known.</param> | ||
| 1811 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
| 1812 | /// <param name="previousType">Type of previous item, if known.</param> | ||
| 1813 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
| 1814 | /// <returns>Identifier for package element.</returns> | ||
| 1815 | private string ParseMspPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
| 1816 | { | ||
| 1817 | return this.ParseChainPackage(node, WixBundlePackageType.Msp, parentType, parentId, previousType, previousId); | ||
| 1818 | } | ||
| 1819 | |||
| 1820 | /// <summary> | ||
| 1821 | /// Parse MsuPackage element | ||
| 1822 | /// </summary> | ||
| 1823 | /// <param name="node">Element to parse</param> | ||
| 1824 | /// <param name="parentType">Type of parent group, if known.</param> | ||
| 1825 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
| 1826 | /// <param name="previousType">Type of previous item, if known.</param> | ||
| 1827 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
| 1828 | /// <returns>Identifier for package element.</returns> | ||
| 1829 | private string ParseMsuPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
| 1830 | { | ||
| 1831 | return this.ParseChainPackage(node, WixBundlePackageType.Msu, parentType, parentId, previousType, previousId); | ||
| 1832 | } | ||
| 1833 | |||
| 1834 | /// <summary> | ||
| 1835 | /// Parse ExePackage element | ||
| 1836 | /// </summary> | ||
| 1837 | /// <param name="node">Element to parse</param> | ||
| 1838 | /// <param name="parentType">Type of parent group, if known.</param> | ||
| 1839 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
| 1840 | /// <param name="previousType">Type of previous item, if known.</param> | ||
| 1841 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
| 1842 | /// <returns>Identifier for package element.</returns> | ||
| 1843 | private string ParseExePackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
| 1844 | { | ||
| 1845 | return this.ParseChainPackage(node, WixBundlePackageType.Exe, parentType, parentId, previousType, previousId); | ||
| 1846 | } | ||
| 1847 | |||
| 1848 | /// <summary> | ||
| 1849 | /// Parse RollbackBoundary element | ||
| 1850 | /// </summary> | ||
| 1851 | /// <param name="node">Element to parse</param> | ||
| 1852 | /// <param name="parentType">Type of parent group, if known.</param> | ||
| 1853 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
| 1854 | /// <param name="previousType">Type of previous item, if known.</param> | ||
| 1855 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
| 1856 | /// <returns>Identifier for package element.</returns> | ||
| 1857 | private string ParseRollbackBoundaryElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
| 1858 | { | ||
| 1859 | Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); | ||
| 1860 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); | ||
| 1861 | |||
| 1862 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1863 | Identifier id = null; | ||
| 1864 | var vital = YesNoType.Yes; | ||
| 1865 | var transaction = YesNoType.No; | ||
| 1866 | |||
| 1867 | // This list lets us evaluate extension attributes *after* all core attributes | ||
| 1868 | // have been parsed and dealt with, regardless of authoring order. | ||
| 1869 | var extensionAttributes = new List<XAttribute>(); | ||
| 1870 | |||
| 1871 | foreach (var attrib in node.Attributes()) | ||
| 1872 | { | ||
| 1873 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1874 | { | ||
| 1875 | var allowed = true; | ||
| 1876 | switch (attrib.Name.LocalName) | ||
| 1877 | { | ||
| 1878 | case "Id": | ||
| 1879 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1880 | if (id?.Id == BurnConstants.BundleDefaultBoundaryId) | ||
| 1881 | { | ||
| 1882 | this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
| 1883 | } | ||
| 1884 | break; | ||
| 1885 | case "Vital": | ||
| 1886 | vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1887 | break; | ||
| 1888 | case "Transaction": | ||
| 1889 | transaction = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1890 | break; | ||
| 1891 | default: | ||
| 1892 | allowed = false; | ||
| 1893 | break; | ||
| 1894 | } | ||
| 1895 | |||
| 1896 | if (!allowed) | ||
| 1897 | { | ||
| 1898 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1899 | } | ||
| 1900 | } | ||
| 1901 | else | ||
| 1902 | { | ||
| 1903 | // Save the extension attributes for later... | ||
| 1904 | extensionAttributes.Add(attrib); | ||
| 1905 | } | ||
| 1906 | } | ||
| 1907 | |||
| 1908 | if (null == id) | ||
| 1909 | { | ||
| 1910 | if (!String.IsNullOrEmpty(previousId)) | ||
| 1911 | { | ||
| 1912 | id = this.Core.CreateIdentifier("rba", previousId); | ||
| 1913 | } | ||
| 1914 | |||
| 1915 | if (null == id) | ||
| 1916 | { | ||
| 1917 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 1918 | id = Identifier.Invalid; | ||
| 1919 | } | ||
| 1920 | else if (!Common.IsIdentifier(id.Id)) | ||
| 1921 | { | ||
| 1922 | this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
| 1923 | } | ||
| 1924 | } | ||
| 1925 | |||
| 1926 | // Now that the rollback identifier is known, we can parse the extension attributes... | ||
| 1927 | var contextValues = new Dictionary<string, string> | ||
| 1928 | { | ||
| 1929 | ["RollbackBoundaryId"] = id.Id | ||
| 1930 | }; | ||
| 1931 | foreach (var attribute in extensionAttributes) | ||
| 1932 | { | ||
| 1933 | this.Core.ParseExtensionAttribute(node, attribute, contextValues); | ||
| 1934 | } | ||
| 1935 | |||
| 1936 | this.Core.ParseForExtensionElements(node); | ||
| 1937 | |||
| 1938 | if (!this.Core.EncounteredError) | ||
| 1939 | { | ||
| 1940 | this.CreateRollbackBoundary(sourceLineNumbers, id, vital, transaction, parentType, parentId, previousType, previousId); | ||
| 1941 | } | ||
| 1942 | |||
| 1943 | return id.Id; | ||
| 1944 | } | ||
| 1945 | |||
| 1946 | /// <summary> | ||
| 1947 | /// Parses one of the ChainPackage elements | ||
| 1948 | /// </summary> | ||
| 1949 | /// <param name="node">Element to parse</param> | ||
| 1950 | /// <param name="packageType">Type of package to parse</param> | ||
| 1951 | /// <param name="parentType">Type of parent group, if known.</param> | ||
| 1952 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
| 1953 | /// <param name="previousType">Type of previous item, if known.</param> | ||
| 1954 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
| 1955 | /// <returns>Identifier for package element.</returns> | ||
| 1956 | /// <remarks>This method contains the shared logic for parsing all of the ChainPackage | ||
| 1957 | /// types, as there is more in common between them than different.</remarks> | ||
| 1958 | private string ParseChainPackage(XElement node, WixBundlePackageType packageType, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
| 1959 | { | ||
| 1960 | Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); | ||
| 1961 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); | ||
| 1962 | |||
| 1963 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1964 | var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) | ||
| 1965 | { | ||
| 1966 | IsRequired = false, | ||
| 1967 | }; | ||
| 1968 | string after = null; | ||
| 1969 | string installCondition = null; | ||
| 1970 | var cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space. | ||
| 1971 | string cacheId = null; | ||
| 1972 | string description = null; | ||
| 1973 | string displayName = null; | ||
| 1974 | var logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; | ||
| 1975 | var rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; | ||
| 1976 | var permanent = YesNoType.NotSet; | ||
| 1977 | var visible = YesNoType.NotSet; | ||
| 1978 | var vital = YesNoType.Yes; | ||
| 1979 | string installArguments = null; | ||
| 1980 | string repairArguments = null; | ||
| 1981 | string uninstallArguments = null; | ||
| 1982 | var perMachine = YesNoDefaultType.NotSet; | ||
| 1983 | string detectCondition = null; | ||
| 1984 | string protocol = null; | ||
| 1985 | var installSize = CompilerConstants.IntegerNotSet; | ||
| 1986 | string msuKB = null; | ||
| 1987 | var enableFeatureSelection = YesNoType.NotSet; | ||
| 1988 | var forcePerMachine = YesNoType.NotSet; | ||
| 1989 | CompilerPayload childPackageCompilerPayload = null; | ||
| 1990 | var slipstream = YesNoType.NotSet; | ||
| 1991 | var hasPayloadInfo = false; | ||
| 1992 | |||
| 1993 | var expectedNetFx4Args = new string[] { "/q", "/norestart" }; | ||
| 1994 | |||
| 1995 | // This list lets us evaluate extension attributes *after* all core attributes | ||
| 1996 | // have been parsed and dealt with, regardless of authoring order. | ||
| 1997 | var extensionAttributes = new List<XAttribute>(); | ||
| 1998 | |||
| 1999 | foreach (var attrib in node.Attributes()) | ||
| 2000 | { | ||
| 2001 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2002 | { | ||
| 2003 | var allowed = true; | ||
| 2004 | switch (attrib.Name.LocalName) | ||
| 2005 | { | ||
| 2006 | case "Id": | ||
| 2007 | compilerPayload.ParseId(attrib); | ||
| 2008 | break; | ||
| 2009 | case "Name": | ||
| 2010 | compilerPayload.ParseName(attrib); | ||
| 2011 | hasPayloadInfo = true; | ||
| 2012 | break; | ||
| 2013 | case "SourceFile": | ||
| 2014 | compilerPayload.ParseSourceFile(attrib); | ||
| 2015 | hasPayloadInfo = true; | ||
| 2016 | break; | ||
| 2017 | case "DownloadUrl": | ||
| 2018 | compilerPayload.ParseDownloadUrl(attrib); | ||
| 2019 | hasPayloadInfo = true; | ||
| 2020 | break; | ||
| 2021 | case "After": | ||
| 2022 | after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2023 | break; | ||
| 2024 | case "InstallCondition": | ||
| 2025 | installCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2026 | break; | ||
| 2027 | case "Cache": | ||
| 2028 | var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2029 | switch (value) | ||
| 2030 | { | ||
| 2031 | case "always": | ||
| 2032 | cache = YesNoAlwaysType.Always; | ||
| 2033 | break; | ||
| 2034 | case "yes": | ||
| 2035 | cache = YesNoAlwaysType.Yes; | ||
| 2036 | break; | ||
| 2037 | case "no": | ||
| 2038 | cache = YesNoAlwaysType.No; | ||
| 2039 | break; | ||
| 2040 | case "": | ||
| 2041 | break; | ||
| 2042 | default: | ||
| 2043 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "always", "yes", "no")); | ||
| 2044 | break; | ||
| 2045 | } | ||
| 2046 | break; | ||
| 2047 | case "CacheId": | ||
| 2048 | cacheId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2049 | break; | ||
| 2050 | case "Description": | ||
| 2051 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2052 | break; | ||
| 2053 | case "DisplayName": | ||
| 2054 | displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2055 | break; | ||
| 2056 | case "EnableFeatureSelection": | ||
| 2057 | enableFeatureSelection = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2058 | allowed = (packageType == WixBundlePackageType.Msi); | ||
| 2059 | break; | ||
| 2060 | case "ForcePerMachine": | ||
| 2061 | forcePerMachine = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2062 | allowed = (packageType == WixBundlePackageType.Msi); | ||
| 2063 | break; | ||
| 2064 | case "LogPathVariable": | ||
| 2065 | logPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 2066 | break; | ||
| 2067 | case "RollbackLogPathVariable": | ||
| 2068 | rollbackPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 2069 | break; | ||
| 2070 | case "Permanent": | ||
| 2071 | permanent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2072 | break; | ||
| 2073 | case "Visible": | ||
| 2074 | visible = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2075 | allowed = (packageType == WixBundlePackageType.Msi); | ||
| 2076 | break; | ||
| 2077 | case "Vital": | ||
| 2078 | vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2079 | break; | ||
| 2080 | case "InstallArguments": | ||
| 2081 | installArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2082 | allowed = (packageType == WixBundlePackageType.Exe); | ||
| 2083 | break; | ||
| 2084 | case "RepairArguments": | ||
| 2085 | repairArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 2086 | allowed = (packageType == WixBundlePackageType.Exe); | ||
| 2087 | break; | ||
| 2088 | case "UninstallArguments": | ||
| 2089 | uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2090 | allowed = (packageType == WixBundlePackageType.Exe); | ||
| 2091 | break; | ||
| 2092 | case "PerMachine": | ||
| 2093 | perMachine = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
| 2094 | allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp); | ||
| 2095 | break; | ||
| 2096 | case "DetectCondition": | ||
| 2097 | detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 2098 | allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu); | ||
| 2099 | break; | ||
| 2100 | case "Protocol": | ||
| 2101 | protocol = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2102 | allowed = (packageType == WixBundlePackageType.Exe); | ||
| 2103 | break; | ||
| 2104 | case "InstallSize": | ||
| 2105 | installSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 2106 | break; | ||
| 2107 | case "KB": | ||
| 2108 | msuKB = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2109 | allowed = (packageType == WixBundlePackageType.Msu); | ||
| 2110 | break; | ||
| 2111 | case "Compressed": | ||
| 2112 | compilerPayload.ParseCompressed(attrib); | ||
| 2113 | hasPayloadInfo = true; | ||
| 2114 | break; | ||
| 2115 | case "Slipstream": | ||
| 2116 | slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2117 | allowed = (packageType == WixBundlePackageType.Msp); | ||
| 2118 | break; | ||
| 2119 | default: | ||
| 2120 | allowed = false; | ||
| 2121 | break; | ||
| 2122 | } | ||
| 2123 | |||
| 2124 | if (!allowed) | ||
| 2125 | { | ||
| 2126 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2127 | } | ||
| 2128 | } | ||
| 2129 | else | ||
| 2130 | { | ||
| 2131 | // Save the extension attributes for later... | ||
| 2132 | extensionAttributes.Add(attrib); | ||
| 2133 | } | ||
| 2134 | } | ||
| 2135 | |||
| 2136 | // We need to handle the package payload up front because it affects Id generation. Id is needed by other child elements. | ||
| 2137 | var packagePayloadElementName = packageType + "PackagePayload"; | ||
| 2138 | foreach (var child in node.Elements(CompilerCore.WixNamespace + packagePayloadElementName)) | ||
| 2139 | { | ||
| 2140 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 2141 | |||
| 2142 | if (childPackageCompilerPayload != null) | ||
| 2143 | { | ||
| 2144 | this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); | ||
| 2145 | } | ||
| 2146 | else if (hasPayloadInfo) | ||
| 2147 | { | ||
| 2148 | this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "SourceFile", "Name", "DownloadUrl", "Compressed")); | ||
| 2149 | } | ||
| 2150 | |||
| 2151 | childPackageCompilerPayload = this.ParsePackagePayloadElement(childSourceLineNumbers, child, packageType, compilerPayload.Id); | ||
| 2152 | } | ||
| 2153 | |||
| 2154 | if (compilerPayload.Id == null && childPackageCompilerPayload != null) | ||
| 2155 | { | ||
| 2156 | compilerPayload.Id = childPackageCompilerPayload.Id; | ||
| 2157 | } | ||
| 2158 | |||
| 2159 | compilerPayload.FinishCompilingPackage(); | ||
| 2160 | var id = compilerPayload.Id; | ||
| 2161 | |||
| 2162 | if (id.Id == BurnConstants.BundleDefaultBoundaryId) | ||
| 2163 | { | ||
| 2164 | this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
| 2165 | } | ||
| 2166 | |||
| 2167 | if (null == logPathVariable) | ||
| 2168 | { | ||
| 2169 | logPathVariable = String.Concat("WixBundleLog_", id.Id); | ||
| 2170 | } | ||
| 2171 | |||
| 2172 | if (null == rollbackPathVariable) | ||
| 2173 | { | ||
| 2174 | rollbackPathVariable = String.Concat("WixBundleRollbackLog_", id.Id); | ||
| 2175 | } | ||
| 2176 | |||
| 2177 | if (!String.IsNullOrEmpty(protocol) && !protocol.Equals("burn", StringComparison.Ordinal) && !protocol.Equals("netfx4", StringComparison.Ordinal) && !protocol.Equals("none", StringComparison.Ordinal)) | ||
| 2178 | { | ||
| 2179 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Protocol", protocol, "none, burn, netfx4")); | ||
| 2180 | } | ||
| 2181 | |||
| 2182 | if (!String.IsNullOrEmpty(protocol) && protocol.Equals("netfx4", StringComparison.Ordinal)) | ||
| 2183 | { | ||
| 2184 | foreach (var expectedArgument in expectedNetFx4Args) | ||
| 2185 | { | ||
| 2186 | if (null == installArguments || -1 == installArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) | ||
| 2187 | { | ||
| 2188 | this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallArguments", installArguments, expectedArgument, "Protocol", "netfx4")); | ||
| 2189 | } | ||
| 2190 | |||
| 2191 | if (!String.IsNullOrEmpty(repairArguments) && -1 == repairArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) | ||
| 2192 | { | ||
| 2193 | this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairArguments", repairArguments, expectedArgument, "Protocol", "netfx4")); | ||
| 2194 | } | ||
| 2195 | |||
| 2196 | if (!String.IsNullOrEmpty(uninstallArguments) && -1 == uninstallArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) | ||
| 2197 | { | ||
| 2198 | this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallArguments", uninstallArguments, expectedArgument, "Protocol", "netfx4")); | ||
| 2199 | } | ||
| 2200 | } | ||
| 2201 | } | ||
| 2202 | |||
| 2203 | // Only set default scope for EXEs and MSPs if not already set. | ||
| 2204 | if ((WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msp == packageType) && YesNoDefaultType.NotSet == perMachine) | ||
| 2205 | { | ||
| 2206 | perMachine = YesNoDefaultType.Default; | ||
| 2207 | } | ||
| 2208 | |||
| 2209 | // Detect condition is recommended or required for Exe and Msu packages | ||
| 2210 | // (depending on whether uninstall arguments were provided). | ||
| 2211 | if ((packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu) && String.IsNullOrEmpty(detectCondition)) | ||
| 2212 | { | ||
| 2213 | if (String.IsNullOrEmpty(uninstallArguments)) | ||
| 2214 | { | ||
| 2215 | this.Core.Write(WarningMessages.DetectConditionRecommended(sourceLineNumbers, node.Name.LocalName)); | ||
| 2216 | } | ||
| 2217 | else | ||
| 2218 | { | ||
| 2219 | this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallArguments")); | ||
| 2220 | } | ||
| 2221 | } | ||
| 2222 | |||
| 2223 | // Now that the package ID is known, we can parse the extension attributes... | ||
| 2224 | var contextValues = new Dictionary<string, string>() { { "PackageId", id.Id } }; | ||
| 2225 | foreach (var attribute in extensionAttributes) | ||
| 2226 | { | ||
| 2227 | this.Core.ParseExtensionAttribute(node, attribute, contextValues); | ||
| 2228 | } | ||
| 2229 | |||
| 2230 | foreach (var child in node.Elements()) | ||
| 2231 | { | ||
| 2232 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 2233 | { | ||
| 2234 | var allowed = true; | ||
| 2235 | switch (child.Name.LocalName) | ||
| 2236 | { | ||
| 2237 | case "SlipstreamMsp": | ||
| 2238 | allowed = (packageType == WixBundlePackageType.Msi); | ||
| 2239 | if (allowed) | ||
| 2240 | { | ||
| 2241 | this.ParseSlipstreamMspElement(child, id.Id); | ||
| 2242 | } | ||
| 2243 | break; | ||
| 2244 | case "MsiProperty": | ||
| 2245 | allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp); | ||
| 2246 | if (allowed) | ||
| 2247 | { | ||
| 2248 | this.ParseMsiPropertyElement(child, id.Id); | ||
| 2249 | } | ||
| 2250 | break; | ||
| 2251 | case "Payload": | ||
| 2252 | this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); | ||
| 2253 | break; | ||
| 2254 | case "PayloadGroupRef": | ||
| 2255 | this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); | ||
| 2256 | break; | ||
| 2257 | case "Provides": | ||
| 2258 | this.ParseProvidesElement(child, packageType, id.Id, out _); | ||
| 2259 | break; | ||
| 2260 | case "ExitCode": | ||
| 2261 | allowed = (packageType == WixBundlePackageType.Exe); | ||
| 2262 | if (allowed) | ||
| 2263 | { | ||
| 2264 | this.ParseExitCodeElement(child, id.Id); | ||
| 2265 | } | ||
| 2266 | break; | ||
| 2267 | case "CommandLine": | ||
| 2268 | allowed = (packageType == WixBundlePackageType.Exe); | ||
| 2269 | if (allowed) | ||
| 2270 | { | ||
| 2271 | this.ParseCommandLineElement(child, id.Id); | ||
| 2272 | } | ||
| 2273 | break; | ||
| 2274 | case "ExePackagePayload": | ||
| 2275 | case "MsiPackagePayload": | ||
| 2276 | case "MspPackagePayload": | ||
| 2277 | case "MsuPackagePayload": | ||
| 2278 | allowed = packagePayloadElementName == child.Name.LocalName; | ||
| 2279 | // Handled previously | ||
| 2280 | break; | ||
| 2281 | default: | ||
| 2282 | allowed = false; | ||
| 2283 | break; | ||
| 2284 | } | ||
| 2285 | |||
| 2286 | if (!allowed) | ||
| 2287 | { | ||
| 2288 | this.Core.UnexpectedElement(node, child); | ||
| 2289 | } | ||
| 2290 | } | ||
| 2291 | else | ||
| 2292 | { | ||
| 2293 | var context = new Dictionary<string, string>() { { "Id", id.Id } }; | ||
| 2294 | this.Core.ParseExtensionElement(node, child, context); | ||
| 2295 | } | ||
| 2296 | } | ||
| 2297 | |||
| 2298 | if (!this.Core.EncounteredError) | ||
| 2299 | { | ||
| 2300 | var packageCompilerPayload = childPackageCompilerPayload ?? (hasPayloadInfo ? compilerPayload : null); | ||
| 2301 | if (packageCompilerPayload != null) | ||
| 2302 | { | ||
| 2303 | var payload = packageCompilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id); | ||
| 2304 | |||
| 2305 | this.CreatePackagePayloadSymbol(sourceLineNumbers, packageType, payload.Id, ComplexReferenceParentType.Package, id); | ||
| 2306 | } | ||
| 2307 | |||
| 2308 | this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); | ||
| 2309 | |||
| 2310 | WixBundlePackageAttributes attributes = 0; | ||
| 2311 | attributes |= (YesNoType.Yes == permanent) ? WixBundlePackageAttributes.Permanent : 0; | ||
| 2312 | attributes |= (YesNoType.Yes == visible) ? WixBundlePackageAttributes.Visible : 0; | ||
| 2313 | |||
| 2314 | var chainPackageSymbol = this.Core.AddSymbol(new WixBundlePackageSymbol(sourceLineNumbers, id) | ||
| 2315 | { | ||
| 2316 | Type = packageType, | ||
| 2317 | Attributes = attributes, | ||
| 2318 | InstallCondition = installCondition, | ||
| 2319 | CacheId = cacheId, | ||
| 2320 | Description = description, | ||
| 2321 | DisplayName = displayName, | ||
| 2322 | LogPathVariable = logPathVariable, | ||
| 2323 | RollbackLogPathVariable = rollbackPathVariable, | ||
| 2324 | }); | ||
| 2325 | |||
| 2326 | if (YesNoAlwaysType.NotSet != cache) | ||
| 2327 | { | ||
| 2328 | chainPackageSymbol.Cache = cache; | ||
| 2329 | } | ||
| 2330 | |||
| 2331 | if (YesNoType.NotSet != vital) | ||
| 2332 | { | ||
| 2333 | chainPackageSymbol.Vital = (vital == YesNoType.Yes); | ||
| 2334 | } | ||
| 2335 | |||
| 2336 | if (YesNoDefaultType.NotSet != perMachine) | ||
| 2337 | { | ||
| 2338 | chainPackageSymbol.PerMachine = perMachine; | ||
| 2339 | } | ||
| 2340 | |||
| 2341 | if (CompilerConstants.IntegerNotSet != installSize) | ||
| 2342 | { | ||
| 2343 | chainPackageSymbol.InstallSize = installSize; | ||
| 2344 | } | ||
| 2345 | |||
| 2346 | switch (packageType) | ||
| 2347 | { | ||
| 2348 | case WixBundlePackageType.Exe: | ||
| 2349 | this.Core.AddSymbol(new WixBundleExePackageSymbol(sourceLineNumbers, id) | ||
| 2350 | { | ||
| 2351 | Attributes = WixBundleExePackageAttributes.None, | ||
| 2352 | DetectCondition = detectCondition, | ||
| 2353 | InstallCommand = installArguments, | ||
| 2354 | RepairCommand = repairArguments, | ||
| 2355 | UninstallCommand = uninstallArguments, | ||
| 2356 | ExeProtocol = protocol | ||
| 2357 | }); | ||
| 2358 | break; | ||
| 2359 | |||
| 2360 | case WixBundlePackageType.Msi: | ||
| 2361 | WixBundleMsiPackageAttributes msiAttributes = 0; | ||
| 2362 | msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0; | ||
| 2363 | msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0; | ||
| 2364 | |||
| 2365 | this.Core.AddSymbol(new WixBundleMsiPackageSymbol(sourceLineNumbers, id) | ||
| 2366 | { | ||
| 2367 | Attributes = msiAttributes | ||
| 2368 | }); | ||
| 2369 | break; | ||
| 2370 | |||
| 2371 | case WixBundlePackageType.Msp: | ||
| 2372 | WixBundleMspPackageAttributes mspAttributes = 0; | ||
| 2373 | mspAttributes |= (YesNoType.Yes == slipstream) ? WixBundleMspPackageAttributes.Slipstream : 0; | ||
| 2374 | |||
| 2375 | this.Core.AddSymbol(new WixBundleMspPackageSymbol(sourceLineNumbers, id) | ||
| 2376 | { | ||
| 2377 | Attributes = mspAttributes | ||
| 2378 | }); | ||
| 2379 | break; | ||
| 2380 | |||
| 2381 | case WixBundlePackageType.Msu: | ||
| 2382 | this.Core.AddSymbol(new WixBundleMsuPackageSymbol(sourceLineNumbers, id) | ||
| 2383 | { | ||
| 2384 | DetectCondition = detectCondition, | ||
| 2385 | MsuKB = msuKB | ||
| 2386 | }); | ||
| 2387 | break; | ||
| 2388 | } | ||
| 2389 | |||
| 2390 | this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after); | ||
| 2391 | this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ContainerPackage, id.Id, ComplexReferenceChildType.Unknown, null); | ||
| 2392 | } | ||
| 2393 | |||
| 2394 | return id.Id; | ||
| 2395 | } | ||
| 2396 | |||
| 2397 | private void CreatePackagePayloadSymbol(SourceLineNumber sourceLineNumbers, WixBundlePackageType packageType, Identifier payloadId, ComplexReferenceParentType parentType, Identifier parentId) | ||
| 2398 | { | ||
| 2399 | switch (packageType) | ||
| 2400 | { | ||
| 2401 | case WixBundlePackageType.Exe: | ||
| 2402 | this.Core.AddSymbol(new WixBundleExePackagePayloadSymbol(sourceLineNumbers, payloadId)); | ||
| 2403 | break; | ||
| 2404 | |||
| 2405 | case WixBundlePackageType.Msi: | ||
| 2406 | this.Core.AddSymbol(new WixBundleMsiPackagePayloadSymbol(sourceLineNumbers, payloadId)); | ||
| 2407 | break; | ||
| 2408 | |||
| 2409 | case WixBundlePackageType.Msp: | ||
| 2410 | this.Core.AddSymbol(new WixBundleMspPackagePayloadSymbol(sourceLineNumbers, payloadId)); | ||
| 2411 | break; | ||
| 2412 | |||
| 2413 | case WixBundlePackageType.Msu: | ||
| 2414 | this.Core.AddSymbol(new WixBundleMsuPackagePayloadSymbol(sourceLineNumbers, payloadId)); | ||
| 2415 | break; | ||
| 2416 | } | ||
| 2417 | |||
| 2418 | this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PackagePayload, payloadId?.Id, ComplexReferenceChildType.Unknown, null); | ||
| 2419 | } | ||
| 2420 | |||
| 2421 | private CompilerPayload ParsePackagePayloadElement(SourceLineNumber sourceLineNumbers, XElement node, WixBundlePackageType packageType, Identifier defaultId) | ||
| 2422 | { | ||
| 2423 | sourceLineNumbers = sourceLineNumbers ?? Preprocessor.GetSourceLineNumbers(node); | ||
| 2424 | var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) | ||
| 2425 | { | ||
| 2426 | Id = defaultId, | ||
| 2427 | IsRemoteAllowed = packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu, | ||
| 2428 | }; | ||
| 2429 | |||
| 2430 | // This list lets us evaluate extension attributes *after* all core attributes | ||
| 2431 | // have been parsed and dealt with, regardless of authoring order. | ||
| 2432 | var extensionAttributes = new List<XAttribute>(); | ||
| 2433 | |||
| 2434 | foreach (var attrib in node.Attributes()) | ||
| 2435 | { | ||
| 2436 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2437 | { | ||
| 2438 | var allowed = true; | ||
| 2439 | switch (attrib.Name.LocalName) | ||
| 2440 | { | ||
| 2441 | case "Id": | ||
| 2442 | compilerPayload.ParseId(attrib); | ||
| 2443 | break; | ||
| 2444 | case "Compressed": | ||
| 2445 | compilerPayload.ParseCompressed(attrib); | ||
| 2446 | break; | ||
| 2447 | case "Name": | ||
| 2448 | compilerPayload.ParseName(attrib); | ||
| 2449 | break; | ||
| 2450 | case "SourceFile": | ||
| 2451 | compilerPayload.ParseSourceFile(attrib); | ||
| 2452 | break; | ||
| 2453 | case "DownloadUrl": | ||
| 2454 | compilerPayload.ParseDownloadUrl(attrib); | ||
| 2455 | break; | ||
| 2456 | case "Description": | ||
| 2457 | allowed = compilerPayload.IsRemoteAllowed; | ||
| 2458 | if (allowed) | ||
| 2459 | { | ||
| 2460 | compilerPayload.ParseDescription(attrib); | ||
| 2461 | } | ||
| 2462 | break; | ||
| 2463 | case "Hash": | ||
| 2464 | allowed = compilerPayload.IsRemoteAllowed; | ||
| 2465 | if (allowed) | ||
| 2466 | { | ||
| 2467 | compilerPayload.ParseHash(attrib); | ||
| 2468 | } | ||
| 2469 | break; | ||
| 2470 | case "ProductName": | ||
| 2471 | allowed = compilerPayload.IsRemoteAllowed; | ||
| 2472 | if (allowed) | ||
| 2473 | { | ||
| 2474 | compilerPayload.ParseProductName(attrib); | ||
| 2475 | } | ||
| 2476 | break; | ||
| 2477 | case "Size": | ||
| 2478 | allowed = compilerPayload.IsRemoteAllowed; | ||
| 2479 | if (allowed) | ||
| 2480 | { | ||
| 2481 | compilerPayload.ParseSize(attrib); | ||
| 2482 | } | ||
| 2483 | break; | ||
| 2484 | case "Version": | ||
| 2485 | allowed = compilerPayload.IsRemoteAllowed; | ||
| 2486 | if (allowed) | ||
| 2487 | { | ||
| 2488 | compilerPayload.ParseVersion(attrib); | ||
| 2489 | } | ||
| 2490 | break; | ||
| 2491 | default: | ||
| 2492 | allowed = false; | ||
| 2493 | break; | ||
| 2494 | } | ||
| 2495 | |||
| 2496 | if (!allowed) | ||
| 2497 | { | ||
| 2498 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2499 | } | ||
| 2500 | } | ||
| 2501 | else | ||
| 2502 | { | ||
| 2503 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2504 | } | ||
| 2505 | } | ||
| 2506 | |||
| 2507 | compilerPayload.FinishCompilingPackagePayload(); | ||
| 2508 | |||
| 2509 | // Now that the PayloadId is known, we can parse the extension attributes. | ||
| 2510 | var context = new Dictionary<string, string> | ||
| 2511 | { | ||
| 2512 | ["Id"] = compilerPayload.Id.Id, | ||
| 2513 | }; | ||
| 2514 | |||
| 2515 | foreach (var extensionAttribute in extensionAttributes) | ||
| 2516 | { | ||
| 2517 | this.Core.ParseExtensionAttribute(node, extensionAttribute, context); | ||
| 2518 | } | ||
| 2519 | |||
| 2520 | this.Core.ParseForExtensionElements(node); | ||
| 2521 | |||
| 2522 | return compilerPayload; | ||
| 2523 | } | ||
| 2524 | |||
| 2525 | /// <summary> | ||
| 2526 | /// Parse CommandLine element. | ||
| 2527 | /// </summary> | ||
| 2528 | /// <param name="node">Element to parse</param> | ||
| 2529 | /// <param name="packageId">Parent packageId</param> | ||
| 2530 | private void ParseCommandLineElement(XElement node, string packageId) | ||
| 2531 | { | ||
| 2532 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2533 | string installArgument = null; | ||
| 2534 | string uninstallArgument = null; | ||
| 2535 | string repairArgument = null; | ||
| 2536 | string condition = null; | ||
| 2537 | |||
| 2538 | foreach (var attrib in node.Attributes()) | ||
| 2539 | { | ||
| 2540 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2541 | { | ||
| 2542 | switch (attrib.Name.LocalName) | ||
| 2543 | { | ||
| 2544 | case "InstallArgument": | ||
| 2545 | installArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2546 | break; | ||
| 2547 | case "UninstallArgument": | ||
| 2548 | uninstallArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2549 | break; | ||
| 2550 | case "RepairArgument": | ||
| 2551 | repairArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2552 | break; | ||
| 2553 | case "Condition": | ||
| 2554 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2555 | break; | ||
| 2556 | default: | ||
| 2557 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2558 | break; | ||
| 2559 | } | ||
| 2560 | } | ||
| 2561 | else | ||
| 2562 | { | ||
| 2563 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2564 | } | ||
| 2565 | } | ||
| 2566 | |||
| 2567 | if (String.IsNullOrEmpty(condition)) | ||
| 2568 | { | ||
| 2569 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); | ||
| 2570 | } | ||
| 2571 | |||
| 2572 | this.Core.ParseForExtensionElements(node); | ||
| 2573 | |||
| 2574 | if (!this.Core.EncounteredError) | ||
| 2575 | { | ||
| 2576 | this.Core.AddSymbol(new WixBundlePackageCommandLineSymbol(sourceLineNumbers) | ||
| 2577 | { | ||
| 2578 | WixBundlePackageRef = packageId, | ||
| 2579 | InstallArgument = installArgument, | ||
| 2580 | UninstallArgument = uninstallArgument, | ||
| 2581 | RepairArgument = repairArgument, | ||
| 2582 | Condition = condition | ||
| 2583 | }); | ||
| 2584 | } | ||
| 2585 | } | ||
| 2586 | |||
| 2587 | /// <summary> | ||
| 2588 | /// Parse PackageGroup element. | ||
| 2589 | /// </summary> | ||
| 2590 | /// <param name="node">Element to parse</param> | ||
| 2591 | private void ParsePackageGroupElement(XElement node) | ||
| 2592 | { | ||
| 2593 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2594 | Identifier id = null; | ||
| 2595 | |||
| 2596 | foreach (var attrib in node.Attributes()) | ||
| 2597 | { | ||
| 2598 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2599 | { | ||
| 2600 | switch (attrib.Name.LocalName) | ||
| 2601 | { | ||
| 2602 | case "Id": | ||
| 2603 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2604 | if (id?.Id == BurnConstants.BundleChainPackageGroupId) | ||
| 2605 | { | ||
| 2606 | this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
| 2607 | } | ||
| 2608 | break; | ||
| 2609 | default: | ||
| 2610 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2611 | break; | ||
| 2612 | } | ||
| 2613 | } | ||
| 2614 | else | ||
| 2615 | { | ||
| 2616 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2617 | } | ||
| 2618 | } | ||
| 2619 | |||
| 2620 | if (null == id) | ||
| 2621 | { | ||
| 2622 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 2623 | id = Identifier.Invalid; | ||
| 2624 | } | ||
| 2625 | |||
| 2626 | var previousType = ComplexReferenceChildType.Unknown; | ||
| 2627 | string previousId = null; | ||
| 2628 | foreach (var child in node.Elements()) | ||
| 2629 | { | ||
| 2630 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 2631 | { | ||
| 2632 | switch (child.Name.LocalName) | ||
| 2633 | { | ||
| 2634 | case "MsiPackage": | ||
| 2635 | previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
| 2636 | previousType = ComplexReferenceChildType.Package; | ||
| 2637 | break; | ||
| 2638 | case "MspPackage": | ||
| 2639 | previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
| 2640 | previousType = ComplexReferenceChildType.Package; | ||
| 2641 | break; | ||
| 2642 | case "MsuPackage": | ||
| 2643 | previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
| 2644 | previousType = ComplexReferenceChildType.Package; | ||
| 2645 | break; | ||
| 2646 | case "ExePackage": | ||
| 2647 | previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
| 2648 | previousType = ComplexReferenceChildType.Package; | ||
| 2649 | break; | ||
| 2650 | case "RollbackBoundary": | ||
| 2651 | previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
| 2652 | previousType = ComplexReferenceChildType.Package; | ||
| 2653 | break; | ||
| 2654 | case "PackageGroupRef": | ||
| 2655 | previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); | ||
| 2656 | previousType = ComplexReferenceChildType.PackageGroup; | ||
| 2657 | break; | ||
| 2658 | default: | ||
| 2659 | this.Core.UnexpectedElement(node, child); | ||
| 2660 | break; | ||
| 2661 | } | ||
| 2662 | } | ||
| 2663 | else | ||
| 2664 | { | ||
| 2665 | this.Core.ParseExtensionElement(node, child); | ||
| 2666 | } | ||
| 2667 | } | ||
| 2668 | |||
| 2669 | |||
| 2670 | if (!this.Core.EncounteredError) | ||
| 2671 | { | ||
| 2672 | this.Core.AddSymbol(new WixBundlePackageGroupSymbol(sourceLineNumbers, id)); | ||
| 2673 | } | ||
| 2674 | } | ||
| 2675 | |||
| 2676 | /// <summary> | ||
| 2677 | /// Parses a package group reference element. | ||
| 2678 | /// </summary> | ||
| 2679 | /// <param name="node">Element to parse.</param> | ||
| 2680 | /// <param name="parentType">ComplexReferenceParentType of parent element (Unknown or PackageGroup).</param> | ||
| 2681 | /// <param name="parentId">Identifier of parent element.</param> | ||
| 2682 | /// <returns>Identifier for package group element.</returns> | ||
| 2683 | private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 2684 | { | ||
| 2685 | return this.ParsePackageGroupRefElement(node, parentType, parentId, ComplexReferenceChildType.Unknown, null); | ||
| 2686 | } | ||
| 2687 | |||
| 2688 | /// <summary> | ||
| 2689 | /// Parses a package group reference element. | ||
| 2690 | /// </summary> | ||
| 2691 | /// <param name="node">Element to parse.</param> | ||
| 2692 | /// <param name="parentType">ComplexReferenceParentType of parent element (Unknown or PackageGroup).</param> | ||
| 2693 | /// <param name="parentId">Identifier of parent element.</param> | ||
| 2694 | /// <param name="previousType"></param> | ||
| 2695 | /// <param name="previousId"></param> | ||
| 2696 | /// <returns>Identifier for package group element.</returns> | ||
| 2697 | private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
| 2698 | { | ||
| 2699 | Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.PackageGroup == parentType || ComplexReferenceParentType.Container == parentType); | ||
| 2700 | Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); | ||
| 2701 | |||
| 2702 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2703 | string id = null; | ||
| 2704 | string after = null; | ||
| 2705 | |||
| 2706 | foreach (var attrib in node.Attributes()) | ||
| 2707 | { | ||
| 2708 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2709 | { | ||
| 2710 | switch (attrib.Name.LocalName) | ||
| 2711 | { | ||
| 2712 | case "Id": | ||
| 2713 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2714 | if (id == BurnConstants.BundleChainPackageGroupId) | ||
| 2715 | { | ||
| 2716 | this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id)); | ||
| 2717 | } | ||
| 2718 | else | ||
| 2719 | { | ||
| 2720 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackageGroup, id); | ||
| 2721 | } | ||
| 2722 | break; | ||
| 2723 | case "After": | ||
| 2724 | after = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2725 | break; | ||
| 2726 | default: | ||
| 2727 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2728 | break; | ||
| 2729 | } | ||
| 2730 | } | ||
| 2731 | else | ||
| 2732 | { | ||
| 2733 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2734 | |||
| 2735 | } | ||
| 2736 | } | ||
| 2737 | |||
| 2738 | if (null == id) | ||
| 2739 | { | ||
| 2740 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 2741 | } | ||
| 2742 | |||
| 2743 | if (null != after && ComplexReferenceParentType.Container == parentType) | ||
| 2744 | { | ||
| 2745 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "After", parentId)); | ||
| 2746 | } | ||
| 2747 | |||
| 2748 | this.Core.ParseForExtensionElements(node); | ||
| 2749 | |||
| 2750 | if (ComplexReferenceParentType.Container == parentType) | ||
| 2751 | { | ||
| 2752 | this.Core.CreateWixGroupRow(sourceLineNumbers, ComplexReferenceParentType.Container, parentId, ComplexReferenceChildType.PackageGroup, id); | ||
| 2753 | } | ||
| 2754 | else | ||
| 2755 | { | ||
| 2756 | this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PackageGroup, id, previousType, previousId, after); | ||
| 2757 | } | ||
| 2758 | |||
| 2759 | return id; | ||
| 2760 | } | ||
| 2761 | |||
| 2762 | /// <summary> | ||
| 2763 | /// Creates rollback boundary. | ||
| 2764 | /// </summary> | ||
| 2765 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
| 2766 | /// <param name="id">Identifier for the rollback boundary.</param> | ||
| 2767 | /// <param name="vital">Indicates whether the rollback boundary is vital or not.</param> | ||
| 2768 | /// <param name="transaction">Indicates whether the rollback boundary will use an MSI transaction.</param> | ||
| 2769 | /// <param name="parentType">Type of parent group.</param> | ||
| 2770 | /// <param name="parentId">Identifier of parent group.</param> | ||
| 2771 | /// <param name="previousType">Type of previous item, if any.</param> | ||
| 2772 | /// <param name="previousId">Identifier of previous item, if any.</param> | ||
| 2773 | private void CreateRollbackBoundary(SourceLineNumber sourceLineNumbers, Identifier id, YesNoType vital, YesNoType transaction, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) | ||
| 2774 | { | ||
| 2775 | this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); | ||
| 2776 | |||
| 2777 | var rollbackBoundary = this.Core.AddSymbol(new WixBundleRollbackBoundarySymbol(sourceLineNumbers, id)); | ||
| 2778 | |||
| 2779 | if (YesNoType.NotSet != vital) | ||
| 2780 | { | ||
| 2781 | rollbackBoundary.Vital = (vital == YesNoType.Yes); | ||
| 2782 | } | ||
| 2783 | |||
| 2784 | if (YesNoType.NotSet != transaction) | ||
| 2785 | { | ||
| 2786 | rollbackBoundary.Transaction = (transaction == YesNoType.Yes); | ||
| 2787 | } | ||
| 2788 | |||
| 2789 | this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, null); | ||
| 2790 | } | ||
| 2791 | |||
| 2792 | /// <summary> | ||
| 2793 | /// Creates group and ordering information for packages | ||
| 2794 | /// </summary> | ||
| 2795 | /// <param name="sourceLineNumbers">Source line numbers.</param> | ||
| 2796 | /// <param name="parentType">Type of parent group, if known.</param> | ||
| 2797 | /// <param name="parentId">Identifier of parent group, if known.</param> | ||
| 2798 | /// <param name="type">Type of this item.</param> | ||
| 2799 | /// <param name="id">Identifier for this item.</param> | ||
| 2800 | /// <param name="previousType">Type of previous item, if known.</param> | ||
| 2801 | /// <param name="previousId">Identifier of previous item, if known</param> | ||
| 2802 | /// <param name="afterId">Identifier of explicit 'After' attribute, if given.</param> | ||
| 2803 | private void CreateChainPackageMetaRows(SourceLineNumber sourceLineNumbers, | ||
| 2804 | ComplexReferenceParentType parentType, string parentId, | ||
| 2805 | ComplexReferenceChildType type, string id, | ||
| 2806 | ComplexReferenceChildType previousType, string previousId, string afterId) | ||
| 2807 | { | ||
| 2808 | // If there's an explicit 'After' attribute, it overrides the inferred previous item. | ||
| 2809 | if (null != afterId) | ||
| 2810 | { | ||
| 2811 | previousType = ComplexReferenceChildType.Package; | ||
| 2812 | previousId = afterId; | ||
| 2813 | } | ||
| 2814 | |||
| 2815 | this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId); | ||
| 2816 | } | ||
| 2817 | |||
| 2818 | /// <summary> | ||
| 2819 | /// Parse MsiProperty element | ||
| 2820 | /// </summary> | ||
| 2821 | /// <param name="node">Element to parse</param> | ||
| 2822 | /// <param name="packageId">Id of parent element</param> | ||
| 2823 | private void ParseMsiPropertyElement(XElement node, string packageId) | ||
| 2824 | { | ||
| 2825 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2826 | string name = null; | ||
| 2827 | string value = null; | ||
| 2828 | string condition = null; | ||
| 2829 | |||
| 2830 | foreach (var attrib in node.Attributes()) | ||
| 2831 | { | ||
| 2832 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2833 | { | ||
| 2834 | switch (attrib.Name.LocalName) | ||
| 2835 | { | ||
| 2836 | case "Name": | ||
| 2837 | name = this.Core.GetAttributeMsiPropertyNameValue(sourceLineNumbers, attrib); | ||
| 2838 | break; | ||
| 2839 | case "Value": | ||
| 2840 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2841 | break; | ||
| 2842 | case "Condition": | ||
| 2843 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2844 | break; | ||
| 2845 | default: | ||
| 2846 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2847 | break; | ||
| 2848 | } | ||
| 2849 | } | ||
| 2850 | else | ||
| 2851 | { | ||
| 2852 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2853 | } | ||
| 2854 | } | ||
| 2855 | |||
| 2856 | if (null == name) | ||
| 2857 | { | ||
| 2858 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 2859 | } | ||
| 2860 | |||
| 2861 | if (null == value) | ||
| 2862 | { | ||
| 2863 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 2864 | } | ||
| 2865 | |||
| 2866 | this.Core.ParseForExtensionElements(node); | ||
| 2867 | |||
| 2868 | if (!this.Core.EncounteredError) | ||
| 2869 | { | ||
| 2870 | var symbol = this.Core.AddSymbol(new WixBundleMsiPropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, name)) | ||
| 2871 | { | ||
| 2872 | PackageRef = packageId, | ||
| 2873 | Name = name, | ||
| 2874 | Value = value | ||
| 2875 | }); | ||
| 2876 | |||
| 2877 | if (!String.IsNullOrEmpty(condition)) | ||
| 2878 | { | ||
| 2879 | symbol.Condition = condition; | ||
| 2880 | } | ||
| 2881 | } | ||
| 2882 | } | ||
| 2883 | |||
| 2884 | /// <summary> | ||
| 2885 | /// Parse SlipstreamMsp element | ||
| 2886 | /// </summary> | ||
| 2887 | /// <param name="node">Element to parse</param> | ||
| 2888 | /// <param name="packageId">Id of parent element</param> | ||
| 2889 | private void ParseSlipstreamMspElement(XElement node, string packageId) | ||
| 2890 | { | ||
| 2891 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2892 | string id = null; | ||
| 2893 | |||
| 2894 | foreach (var attrib in node.Attributes()) | ||
| 2895 | { | ||
| 2896 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2897 | { | ||
| 2898 | switch (attrib.Name.LocalName) | ||
| 2899 | { | ||
| 2900 | case "Id": | ||
| 2901 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2902 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackage, id); | ||
| 2903 | break; | ||
| 2904 | default: | ||
| 2905 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2906 | break; | ||
| 2907 | } | ||
| 2908 | } | ||
| 2909 | else | ||
| 2910 | { | ||
| 2911 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2912 | } | ||
| 2913 | } | ||
| 2914 | |||
| 2915 | if (null == id) | ||
| 2916 | { | ||
| 2917 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 2918 | } | ||
| 2919 | |||
| 2920 | this.Core.ParseForExtensionElements(node); | ||
| 2921 | |||
| 2922 | if (!this.Core.EncounteredError) | ||
| 2923 | { | ||
| 2924 | this.Core.AddSymbol(new WixBundleSlipstreamMspSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, id)) | ||
| 2925 | { | ||
| 2926 | TargetPackageRef = packageId, | ||
| 2927 | MspPackageRef = id | ||
| 2928 | }); | ||
| 2929 | } | ||
| 2930 | } | ||
| 2931 | |||
| 2932 | /// <summary> | ||
| 2933 | /// Parse RelatedBundle element | ||
| 2934 | /// </summary> | ||
| 2935 | /// <param name="node">Element to parse</param> | ||
| 2936 | private void ParseRelatedBundleElement(XElement node) | ||
| 2937 | { | ||
| 2938 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2939 | string id = null; | ||
| 2940 | var actionType = RelatedBundleActionType.Detect; | ||
| 2941 | |||
| 2942 | foreach (var attrib in node.Attributes()) | ||
| 2943 | { | ||
| 2944 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2945 | { | ||
| 2946 | switch (attrib.Name.LocalName) | ||
| 2947 | { | ||
| 2948 | case "Id": | ||
| 2949 | id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 2950 | break; | ||
| 2951 | case "Action": | ||
| 2952 | var action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2953 | switch (action) | ||
| 2954 | { | ||
| 2955 | case "Detect": | ||
| 2956 | case "detect": | ||
| 2957 | actionType = RelatedBundleActionType.Detect; | ||
| 2958 | break; | ||
| 2959 | case "Upgrade": | ||
| 2960 | case "upgrade": | ||
| 2961 | actionType = RelatedBundleActionType.Upgrade; | ||
| 2962 | break; | ||
| 2963 | case "Addon": | ||
| 2964 | case "addon": | ||
| 2965 | actionType = RelatedBundleActionType.Addon; | ||
| 2966 | break; | ||
| 2967 | case "Patch": | ||
| 2968 | case "patch": | ||
| 2969 | actionType = RelatedBundleActionType.Patch; | ||
| 2970 | break; | ||
| 2971 | case "": | ||
| 2972 | break; | ||
| 2973 | default: | ||
| 2974 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", action, "Detect", "Upgrade", "Addon", "Patch")); | ||
| 2975 | break; | ||
| 2976 | } | ||
| 2977 | break; | ||
| 2978 | default: | ||
| 2979 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2980 | break; | ||
| 2981 | } | ||
| 2982 | } | ||
| 2983 | else | ||
| 2984 | { | ||
| 2985 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2986 | } | ||
| 2987 | } | ||
| 2988 | |||
| 2989 | if (null == id) | ||
| 2990 | { | ||
| 2991 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 2992 | } | ||
| 2993 | |||
| 2994 | this.Core.ParseForExtensionElements(node); | ||
| 2995 | |||
| 2996 | if (!this.Core.EncounteredError) | ||
| 2997 | { | ||
| 2998 | this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) | ||
| 2999 | { | ||
| 3000 | BundleId = id, | ||
| 3001 | Action = actionType, | ||
| 3002 | }); | ||
| 3003 | } | ||
| 3004 | } | ||
| 3005 | |||
| 3006 | /// <summary> | ||
| 3007 | /// Parse Update element | ||
| 3008 | /// </summary> | ||
| 3009 | /// <param name="node">Element to parse</param> | ||
| 3010 | private void ParseUpdateElement(XElement node) | ||
| 3011 | { | ||
| 3012 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3013 | string location = null; | ||
| 3014 | |||
| 3015 | foreach (var attrib in node.Attributes()) | ||
| 3016 | { | ||
| 3017 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3018 | { | ||
| 3019 | switch (attrib.Name.LocalName) | ||
| 3020 | { | ||
| 3021 | case "Location": | ||
| 3022 | location = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3023 | break; | ||
| 3024 | default: | ||
| 3025 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3026 | break; | ||
| 3027 | } | ||
| 3028 | } | ||
| 3029 | else | ||
| 3030 | { | ||
| 3031 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3032 | } | ||
| 3033 | } | ||
| 3034 | |||
| 3035 | if (null == location) | ||
| 3036 | { | ||
| 3037 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Location")); | ||
| 3038 | } | ||
| 3039 | |||
| 3040 | this.Core.ParseForExtensionElements(node); | ||
| 3041 | |||
| 3042 | if (!this.Core.EncounteredError) | ||
| 3043 | { | ||
| 3044 | this.Core.AddSymbol(new WixBundleUpdateSymbol(sourceLineNumbers) | ||
| 3045 | { | ||
| 3046 | Location = location | ||
| 3047 | }); | ||
| 3048 | } | ||
| 3049 | } | ||
| 3050 | |||
| 3051 | /// <summary> | ||
| 3052 | /// Parse SetVariable element | ||
| 3053 | /// </summary> | ||
| 3054 | /// <param name="node">Element to parse</param> | ||
| 3055 | private void ParseSetVariableElement(XElement node) | ||
| 3056 | { | ||
| 3057 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3058 | Identifier id = null; | ||
| 3059 | string variable = null; | ||
| 3060 | string condition = null; | ||
| 3061 | string after = null; | ||
| 3062 | string value = null; | ||
| 3063 | string typeValue = null; | ||
| 3064 | |||
| 3065 | foreach (var attrib in node.Attributes()) | ||
| 3066 | { | ||
| 3067 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3068 | { | ||
| 3069 | switch (attrib.Name.LocalName) | ||
| 3070 | { | ||
| 3071 | case "Id": | ||
| 3072 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 3073 | break; | ||
| 3074 | case "Variable": | ||
| 3075 | variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3076 | break; | ||
| 3077 | case "Condition": | ||
| 3078 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3079 | break; | ||
| 3080 | case "After": | ||
| 3081 | after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3082 | break; | ||
| 3083 | case "Value": | ||
| 3084 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3085 | break; | ||
| 3086 | case "Type": | ||
| 3087 | typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3088 | break; | ||
| 3089 | |||
| 3090 | default: | ||
| 3091 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3092 | break; | ||
| 3093 | } | ||
| 3094 | } | ||
| 3095 | else | ||
| 3096 | { | ||
| 3097 | this.Core.ParseExtensionAttribute(node, attrib, null); | ||
| 3098 | } | ||
| 3099 | } | ||
| 3100 | |||
| 3101 | var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); | ||
| 3102 | |||
| 3103 | this.Core.ParseForExtensionElements(node); | ||
| 3104 | |||
| 3105 | if (id == null) | ||
| 3106 | { | ||
| 3107 | id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type.ToString()); | ||
| 3108 | } | ||
| 3109 | |||
| 3110 | this.Core.CreateWixSearchSymbol(sourceLineNumbers, node.Name.LocalName, id, variable, condition, after); | ||
| 3111 | |||
| 3112 | if (!this.Messaging.EncounteredError) | ||
| 3113 | { | ||
| 3114 | this.Core.AddSymbol(new WixSetVariableSymbol(sourceLineNumbers, id) | ||
| 3115 | { | ||
| 3116 | Value = value, | ||
| 3117 | Type = type, | ||
| 3118 | }); | ||
| 3119 | } | ||
| 3120 | } | ||
| 3121 | |||
| 3122 | /// <summary> | ||
| 3123 | /// Parse Variable element | ||
| 3124 | /// </summary> | ||
| 3125 | /// <param name="node">Element to parse</param> | ||
| 3126 | private void ParseVariableElement(XElement node) | ||
| 3127 | { | ||
| 3128 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3129 | var hidden = false; | ||
| 3130 | string name = null; | ||
| 3131 | var persisted = false; | ||
| 3132 | string value = null; | ||
| 3133 | string typeValue = null; | ||
| 3134 | |||
| 3135 | foreach (var attrib in node.Attributes()) | ||
| 3136 | { | ||
| 3137 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3138 | { | ||
| 3139 | switch (attrib.Name.LocalName) | ||
| 3140 | { | ||
| 3141 | case "Hidden": | ||
| 3142 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 3143 | { | ||
| 3144 | hidden = true; | ||
| 3145 | } | ||
| 3146 | break; | ||
| 3147 | case "Name": | ||
| 3148 | name = this.Core.GetAttributeBundleVariableValue(sourceLineNumbers, attrib); | ||
| 3149 | break; | ||
| 3150 | case "Persisted": | ||
| 3151 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 3152 | { | ||
| 3153 | persisted = true; | ||
| 3154 | } | ||
| 3155 | break; | ||
| 3156 | case "Value": | ||
| 3157 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 3158 | break; | ||
| 3159 | case "Type": | ||
| 3160 | typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3161 | break; | ||
| 3162 | default: | ||
| 3163 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3164 | break; | ||
| 3165 | } | ||
| 3166 | } | ||
| 3167 | else | ||
| 3168 | { | ||
| 3169 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3170 | } | ||
| 3171 | } | ||
| 3172 | |||
| 3173 | if (null == name) | ||
| 3174 | { | ||
| 3175 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 3176 | } | ||
| 3177 | else if (name.StartsWith("Wix", StringComparison.OrdinalIgnoreCase)) | ||
| 3178 | { | ||
| 3179 | this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); | ||
| 3180 | } | ||
| 3181 | |||
| 3182 | if (hidden && persisted) | ||
| 3183 | { | ||
| 3184 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "yes", "Persisted")); | ||
| 3185 | } | ||
| 3186 | |||
| 3187 | var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); | ||
| 3188 | |||
| 3189 | this.Core.ParseForExtensionElements(node); | ||
| 3190 | |||
| 3191 | if (!this.Core.EncounteredError) | ||
| 3192 | { | ||
| 3193 | this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) | ||
| 3194 | { | ||
| 3195 | Value = value, | ||
| 3196 | Type = type, | ||
| 3197 | Hidden = hidden, | ||
| 3198 | Persisted = persisted | ||
| 3199 | }); | ||
| 3200 | } | ||
| 3201 | } | ||
| 3202 | |||
| 3203 | private WixBundleVariableType ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, XElement node, string typeValue, string value) | ||
| 3204 | { | ||
| 3205 | WixBundleVariableType type; | ||
| 3206 | switch (typeValue) | ||
| 3207 | { | ||
| 3208 | case "formatted": | ||
| 3209 | type = WixBundleVariableType.Formatted; | ||
| 3210 | break; | ||
| 3211 | case "numeric": | ||
| 3212 | type = WixBundleVariableType.Numeric; | ||
| 3213 | break; | ||
| 3214 | case "string": | ||
| 3215 | type = WixBundleVariableType.String; | ||
| 3216 | break; | ||
| 3217 | case "version": | ||
| 3218 | type = WixBundleVariableType.Version; | ||
| 3219 | break; | ||
| 3220 | case null: | ||
| 3221 | type = WixBundleVariableType.Unknown; | ||
| 3222 | break; | ||
| 3223 | default: | ||
| 3224 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "formatted", "numeric", "string", "version")); | ||
| 3225 | return WixBundleVariableType.Unknown; | ||
| 3226 | } | ||
| 3227 | |||
| 3228 | if (type != WixBundleVariableType.Unknown) | ||
| 3229 | { | ||
| 3230 | if (value == null) | ||
| 3231 | { | ||
| 3232 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); | ||
| 3233 | } | ||
| 3234 | |||
| 3235 | return type; | ||
| 3236 | } | ||
| 3237 | else if (value == null) | ||
| 3238 | { | ||
| 3239 | return type; | ||
| 3240 | } | ||
| 3241 | |||
| 3242 | // Infer the type from the current value... | ||
| 3243 | if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) | ||
| 3244 | { | ||
| 3245 | // Version constructor does not support simple "v#" syntax so check to see if the value is | ||
| 3246 | // non-negative real quick. | ||
| 3247 | if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _)) | ||
| 3248 | { | ||
| 3249 | return WixBundleVariableType.Version; | ||
| 3250 | } | ||
| 3251 | else if (Version.TryParse(value.Substring(1), out var _)) | ||
| 3252 | { | ||
| 3253 | return WixBundleVariableType.Version; | ||
| 3254 | } | ||
| 3255 | } | ||
| 3256 | |||
| 3257 | // Not a version, check for numeric. | ||
| 3258 | if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _)) | ||
| 3259 | { | ||
| 3260 | return WixBundleVariableType.Numeric; | ||
| 3261 | } | ||
| 3262 | |||
| 3263 | return WixBundleVariableType.String; | ||
| 3264 | } | ||
| 3265 | } | ||
| 3266 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler_Dependency.cs b/src/wix/WixToolset.Core/Compiler_Dependency.cs new file mode 100644 index 00000000..7c863883 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Dependency.cs | |||
| @@ -0,0 +1,384 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml.Linq; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Symbols; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Compiler of the WiX toolset. | ||
| 12 | /// </summary> | ||
| 13 | internal partial class Compiler : ICompiler | ||
| 14 | { | ||
| 15 | // The root registry key for the dependency extension. We write to Software\Classes explicitly | ||
| 16 | // based on the current security context instead of HKCR. See | ||
| 17 | // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. | ||
| 18 | private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; | ||
| 19 | |||
| 20 | private static readonly char[] InvalidDependencyCharacters = new char[] { ' ', '\"', ';', '\\' }; | ||
| 21 | |||
| 22 | /// <summary> | ||
| 23 | /// Processes the ProviderKey bundle attribute. | ||
| 24 | /// </summary> | ||
| 25 | /// <param name="sourceLineNumbers">Source line number for the parent element.</param> | ||
| 26 | /// <param name="parentElement">Parent element of attribute.</param> | ||
| 27 | /// <param name="attribute">The XML attribute for the ProviderKey attribute.</param> | ||
| 28 | private void ParseBundleProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute) | ||
| 29 | { | ||
| 30 | var providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 31 | int illegalChar; | ||
| 32 | |||
| 33 | // Make sure the key does not contain any illegal characters or values. | ||
| 34 | if (String.IsNullOrEmpty(providerKey)) | ||
| 35 | { | ||
| 36 | this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName)); | ||
| 37 | } | ||
| 38 | else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) | ||
| 39 | { | ||
| 40 | this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); | ||
| 41 | } | ||
| 42 | else if ("ALL" == providerKey) | ||
| 43 | { | ||
| 44 | this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey)); | ||
| 45 | } | ||
| 46 | |||
| 47 | if (!this.Messaging.EncounteredError) | ||
| 48 | { | ||
| 49 | // Generate the primary key for the row. | ||
| 50 | var id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey); | ||
| 51 | |||
| 52 | // Create the provider symbol for the bundle. The Component_ field is required | ||
| 53 | // in the table definition but unused for bundles, so just set it to the valid ID. | ||
| 54 | this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) | ||
| 55 | { | ||
| 56 | ParentRef = id.Id, | ||
| 57 | ProviderKey = providerKey, | ||
| 58 | Attributes = WixDependencyProviderAttributes.ProvidesAttributesBundle, | ||
| 59 | }); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | /// <summary> | ||
| 64 | /// Processes the Provides element. | ||
| 65 | /// </summary> | ||
| 66 | /// <param name="node">The XML node for the Provides element.</param> | ||
| 67 | /// <param name="packageType">The type of the package being chained into a bundle, or null if building an MSI package.</param> | ||
| 68 | /// <param name="parentId">The identifier of the parent component or package.</param> | ||
| 69 | /// <param name="possibleKeyPath">Possible KeyPath identifier.</param> | ||
| 70 | /// <returns>Yes if this is the keypath.</returns> | ||
| 71 | private YesNoType ParseProvidesElement(XElement node, WixBundlePackageType? packageType, string parentId, out string possibleKeyPath) | ||
| 72 | { | ||
| 73 | possibleKeyPath = null; | ||
| 74 | |||
| 75 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 76 | Identifier id = null; | ||
| 77 | string key = null; | ||
| 78 | string version = null; | ||
| 79 | string displayName = null; | ||
| 80 | |||
| 81 | foreach (var attrib in node.Attributes()) | ||
| 82 | { | ||
| 83 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 84 | { | ||
| 85 | switch (attrib.Name.LocalName) | ||
| 86 | { | ||
| 87 | case "Id": | ||
| 88 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 89 | break; | ||
| 90 | case "Key": | ||
| 91 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 92 | break; | ||
| 93 | case "Version": | ||
| 94 | version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 95 | break; | ||
| 96 | case "DisplayName": | ||
| 97 | displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 98 | break; | ||
| 99 | default: | ||
| 100 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 101 | break; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | else | ||
| 105 | { | ||
| 106 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | // Make sure the key is valid. The key will default to the ProductCode for MSI packages | ||
| 111 | // and the package code for MSP packages in the binder if not specified. | ||
| 112 | if (!String.IsNullOrEmpty(key)) | ||
| 113 | { | ||
| 114 | int illegalChar; | ||
| 115 | |||
| 116 | // Make sure the key does not contain any illegal characters or values. | ||
| 117 | if (0 <= (illegalChar = key.IndexOfAny(InvalidDependencyCharacters))) | ||
| 118 | { | ||
| 119 | this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], String.Join(" ", InvalidDependencyCharacters))); | ||
| 120 | } | ||
| 121 | else if ("ALL" == key) | ||
| 122 | { | ||
| 123 | this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key)); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | else if (!packageType.HasValue) | ||
| 127 | { | ||
| 128 | // Make sure the ProductCode is authored and set the key. | ||
| 129 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "ProductCode"); | ||
| 130 | key = "!(bind.property.ProductCode)"; | ||
| 131 | } | ||
| 132 | else if (WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msu == packageType) | ||
| 133 | { | ||
| 134 | // Must specify the provider key when authored for a package. | ||
| 135 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 136 | } | ||
| 137 | |||
| 138 | // The Version attribute should not be authored in or for an MSI package. | ||
| 139 | if (!String.IsNullOrEmpty(version)) | ||
| 140 | { | ||
| 141 | switch (packageType) | ||
| 142 | { | ||
| 143 | case null: | ||
| 144 | this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers)); | ||
| 145 | break; | ||
| 146 | case WixBundlePackageType.Msi: | ||
| 147 | this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId)); | ||
| 148 | break; | ||
| 149 | } | ||
| 150 | } | ||
| 151 | else if (WixBundlePackageType.Msp == packageType || WixBundlePackageType.Msu == packageType) | ||
| 152 | { | ||
| 153 | // Must specify the Version when authored for packages that do not contain a version. | ||
| 154 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
| 155 | } | ||
| 156 | |||
| 157 | // Need the element ID for child element processing, so generate now if not authored. | ||
| 158 | if (null == id) | ||
| 159 | { | ||
| 160 | id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key); | ||
| 161 | } | ||
| 162 | |||
| 163 | foreach (var child in node.Elements()) | ||
| 164 | { | ||
| 165 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 166 | { | ||
| 167 | switch (child.Name.LocalName) | ||
| 168 | { | ||
| 169 | case "Requires": | ||
| 170 | this.ParseRequiresElement(child, id.Id); | ||
| 171 | break; | ||
| 172 | case "RequiresRef": | ||
| 173 | this.ParseRequiresRefElement(child, id.Id, requiresAction: !packageType.HasValue); | ||
| 174 | break; | ||
| 175 | default: | ||
| 176 | this.Core.UnexpectedElement(node, child); | ||
| 177 | break; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | else | ||
| 181 | { | ||
| 182 | this.Core.ParseExtensionElement(node, child); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | if (!this.Messaging.EncounteredError) | ||
| 187 | { | ||
| 188 | var symbol = this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) | ||
| 189 | { | ||
| 190 | ParentRef = parentId, | ||
| 191 | ProviderKey = key, | ||
| 192 | }); | ||
| 193 | |||
| 194 | if (!String.IsNullOrEmpty(version)) | ||
| 195 | { | ||
| 196 | symbol.Version = version; | ||
| 197 | } | ||
| 198 | |||
| 199 | if (!String.IsNullOrEmpty(displayName)) | ||
| 200 | { | ||
| 201 | symbol.DisplayName = displayName; | ||
| 202 | } | ||
| 203 | |||
| 204 | if (!packageType.HasValue) | ||
| 205 | { | ||
| 206 | // Generate registry rows for the provider using binder properties. | ||
| 207 | var keyProvides = String.Concat(DependencyRegistryRoot, key); | ||
| 208 | var root = RegistryRootType.MachineUser; | ||
| 209 | |||
| 210 | var value = "[ProductCode]"; | ||
| 211 | this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, null, value, parentId); | ||
| 212 | |||
| 213 | value = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]"; | ||
| 214 | var versionRegistrySymbol = this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "Version", value, parentId); | ||
| 215 | |||
| 216 | value = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]"; | ||
| 217 | this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "DisplayName", value, parentId); | ||
| 218 | |||
| 219 | // Use the Version registry value and use that as a potential key path. | ||
| 220 | possibleKeyPath = versionRegistrySymbol.Id; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | return YesNoType.NotSet; | ||
| 225 | } | ||
| 226 | |||
| 227 | /// <summary> | ||
| 228 | /// Processes the Requires element. | ||
| 229 | /// </summary> | ||
| 230 | /// <param name="node">The XML node for the Requires element.</param> | ||
| 231 | /// <param name="providerId">The parent provider identifier.</param> | ||
| 232 | private void ParseRequiresElement(XElement node, string providerId) | ||
| 233 | { | ||
| 234 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 235 | Identifier id = null; | ||
| 236 | string providerKey = null; | ||
| 237 | string minVersion = null; | ||
| 238 | string maxVersion = null; | ||
| 239 | var attributes = WixDependencySymbolAttributes.None; | ||
| 240 | var illegalChar = -1; | ||
| 241 | |||
| 242 | foreach (var attrib in node.Attributes()) | ||
| 243 | { | ||
| 244 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 245 | { | ||
| 246 | switch (attrib.Name.LocalName) | ||
| 247 | { | ||
| 248 | case "Id": | ||
| 249 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 250 | break; | ||
| 251 | case "ProviderKey": | ||
| 252 | providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 253 | break; | ||
| 254 | case "Minimum": | ||
| 255 | minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 256 | break; | ||
| 257 | case "Maximum": | ||
| 258 | maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 259 | break; | ||
| 260 | case "IncludeMinimum": | ||
| 261 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 262 | { | ||
| 263 | attributes |= WixDependencySymbolAttributes.RequiresAttributesMinVersionInclusive; | ||
| 264 | } | ||
| 265 | break; | ||
| 266 | case "IncludeMaximum": | ||
| 267 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 268 | { | ||
| 269 | attributes |= WixDependencySymbolAttributes.RequiresAttributesMaxVersionInclusive; | ||
| 270 | } | ||
| 271 | break; | ||
| 272 | default: | ||
| 273 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 274 | break; | ||
| 275 | } | ||
| 276 | } | ||
| 277 | else | ||
| 278 | { | ||
| 279 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | this.Core.ParseForExtensionElements(node); | ||
| 284 | |||
| 285 | if (null == id) | ||
| 286 | { | ||
| 287 | // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef | ||
| 288 | // element will be necessary and the Id attribute will be required. | ||
| 289 | if (!String.IsNullOrEmpty(providerId)) | ||
| 290 | { | ||
| 291 | id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey); | ||
| 292 | } | ||
| 293 | else | ||
| 294 | { | ||
| 295 | this.Messaging.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides")); | ||
| 296 | id = Identifier.Invalid; | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | if (String.IsNullOrEmpty(providerKey)) | ||
| 301 | { | ||
| 302 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey")); | ||
| 303 | } | ||
| 304 | // Make sure the key does not contain any illegal characters. | ||
| 305 | else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) | ||
| 306 | { | ||
| 307 | this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); | ||
| 308 | } | ||
| 309 | |||
| 310 | if (!this.Messaging.EncounteredError) | ||
| 311 | { | ||
| 312 | this.Core.AddSymbol(new WixDependencySymbol(sourceLineNumbers, id) | ||
| 313 | { | ||
| 314 | ProviderKey = providerKey, | ||
| 315 | MinVersion = minVersion, | ||
| 316 | MaxVersion = maxVersion, | ||
| 317 | Attributes = attributes | ||
| 318 | }); | ||
| 319 | |||
| 320 | // Create the relationship between this WixDependency symbol and the WixDependencyProvider symbol. | ||
| 321 | if (!String.IsNullOrEmpty(providerId)) | ||
| 322 | { | ||
| 323 | this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) | ||
| 324 | { | ||
| 325 | WixDependencyProviderRef = providerId, | ||
| 326 | WixDependencyRef = id.Id, | ||
| 327 | }); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | /// <summary> | ||
| 333 | /// Processes the RequiresRef element. | ||
| 334 | /// </summary> | ||
| 335 | /// <param name="node">The XML node for the RequiresRef element.</param> | ||
| 336 | /// <param name="providerId">The parent provider identifier.</param> | ||
| 337 | /// <param name="requiresAction">Whether the Requires custom action should be referenced.</param> | ||
| 338 | private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction) | ||
| 339 | { | ||
| 340 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 341 | string id = null; | ||
| 342 | |||
| 343 | foreach (var attrib in node.Attributes()) | ||
| 344 | { | ||
| 345 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 346 | { | ||
| 347 | switch (attrib.Name.LocalName) | ||
| 348 | { | ||
| 349 | case "Id": | ||
| 350 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 351 | break; | ||
| 352 | default: | ||
| 353 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 354 | break; | ||
| 355 | } | ||
| 356 | } | ||
| 357 | else | ||
| 358 | { | ||
| 359 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 360 | } | ||
| 361 | } | ||
| 362 | |||
| 363 | this.Core.ParseForExtensionElements(node); | ||
| 364 | |||
| 365 | if (String.IsNullOrEmpty(id)) | ||
| 366 | { | ||
| 367 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 368 | } | ||
| 369 | |||
| 370 | if (!this.Messaging.EncounteredError) | ||
| 371 | { | ||
| 372 | // Create a link dependency on the row that contains information we'll need during bind. | ||
| 373 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixDependency, id); | ||
| 374 | |||
| 375 | // Create the relationship between the WixDependency row and the parent WixDependencyProvider row. | ||
| 376 | this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) | ||
| 377 | { | ||
| 378 | WixDependencyProviderRef = providerId, | ||
| 379 | WixDependencyRef = id, | ||
| 380 | }); | ||
| 381 | } | ||
| 382 | } | ||
| 383 | } | ||
| 384 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs b/src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs new file mode 100644 index 00000000..ede03933 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs | |||
| @@ -0,0 +1,417 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Xml.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Data.WindowsInstaller; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Compiler of the WiX toolset. | ||
| 14 | /// </summary> | ||
| 15 | internal partial class Compiler : ICompiler | ||
| 16 | { | ||
| 17 | /// <summary> | ||
| 18 | /// Parses an EmbeddedChaniner element. | ||
| 19 | /// </summary> | ||
| 20 | /// <param name="node">Element to parse.</param> | ||
| 21 | private void ParseEmbeddedChainerElement(XElement node) | ||
| 22 | { | ||
| 23 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 24 | Identifier id = null; | ||
| 25 | string commandLine = null; | ||
| 26 | string condition = null; | ||
| 27 | string source = null; | ||
| 28 | var type = 0; | ||
| 29 | |||
| 30 | foreach (var attrib in node.Attributes()) | ||
| 31 | { | ||
| 32 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 33 | { | ||
| 34 | switch (attrib.Name.LocalName) | ||
| 35 | { | ||
| 36 | case "Id": | ||
| 37 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 38 | break; | ||
| 39 | case "BinarySource": | ||
| 40 | if (null != source) | ||
| 41 | { | ||
| 42 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "FileSource", "PropertySource")); | ||
| 43 | } | ||
| 44 | source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 45 | type = 0x2; | ||
| 46 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary | ||
| 47 | break; | ||
| 48 | case "CommandLine": | ||
| 49 | commandLine = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 50 | break; | ||
| 51 | case "Condition": | ||
| 52 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 53 | break; | ||
| 54 | case "FileSource": | ||
| 55 | if (null != source) | ||
| 56 | { | ||
| 57 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "PropertySource")); | ||
| 58 | } | ||
| 59 | source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 60 | type = 0x12; | ||
| 61 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File | ||
| 62 | break; | ||
| 63 | case "PropertySource": | ||
| 64 | if (null != source) | ||
| 65 | { | ||
| 66 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "FileSource")); | ||
| 67 | } | ||
| 68 | source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 69 | type = 0x32; | ||
| 70 | // cannot add a reference to a Property because it may be created at runtime. | ||
| 71 | break; | ||
| 72 | default: | ||
| 73 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 74 | break; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | else | ||
| 78 | { | ||
| 79 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | if (null == id) | ||
| 84 | { | ||
| 85 | id = this.Core.CreateIdentifier("mec", source, type.ToString()); | ||
| 86 | } | ||
| 87 | |||
| 88 | if (null == source) | ||
| 89 | { | ||
| 90 | this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "BinarySource", "FileSource", "PropertySource")); | ||
| 91 | } | ||
| 92 | |||
| 93 | this.Core.ParseForExtensionElements(node); | ||
| 94 | |||
| 95 | if (!this.Core.EncounteredError) | ||
| 96 | { | ||
| 97 | this.Core.AddSymbol(new MsiEmbeddedChainerSymbol(sourceLineNumbers, id) | ||
| 98 | { | ||
| 99 | Condition = condition, | ||
| 100 | CommandLine = commandLine, | ||
| 101 | Source = source, | ||
| 102 | Type = type | ||
| 103 | }); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | /// <summary> | ||
| 108 | /// Parses an EmbeddedUI element. | ||
| 109 | /// </summary> | ||
| 110 | /// <param name="node">Element to parse.</param> | ||
| 111 | private void ParseEmbeddedUIElement(XElement node) | ||
| 112 | { | ||
| 113 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 114 | Identifier id = null; | ||
| 115 | string name = null; | ||
| 116 | var supportsBasicUI = false; | ||
| 117 | var messageFilter = WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT | WindowsInstallerConstants.INSTALLLOGMODE_ERROR | WindowsInstallerConstants.INSTALLLOGMODE_WARNING | WindowsInstallerConstants.INSTALLLOGMODE_USER | ||
| 118 | | WindowsInstallerConstants.INSTALLLOGMODE_INFO | WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE | WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE | ||
| 119 | | WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA | ||
| 120 | | WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS | WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA | WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE | ||
| 121 | | WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE | WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG | WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE | ||
| 122 | | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; | ||
| 123 | string sourceFile = null; | ||
| 124 | |||
| 125 | foreach (var attrib in node.Attributes()) | ||
| 126 | { | ||
| 127 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 128 | { | ||
| 129 | switch (attrib.Name.LocalName) | ||
| 130 | { | ||
| 131 | case "Id": | ||
| 132 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 133 | break; | ||
| 134 | case "Name": | ||
| 135 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 136 | break; | ||
| 137 | case "IgnoreFatalExit": | ||
| 138 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 139 | { | ||
| 140 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT; | ||
| 141 | } | ||
| 142 | break; | ||
| 143 | case "IgnoreError": | ||
| 144 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 145 | { | ||
| 146 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ERROR; | ||
| 147 | } | ||
| 148 | break; | ||
| 149 | case "IgnoreWarning": | ||
| 150 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 151 | { | ||
| 152 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_WARNING; | ||
| 153 | } | ||
| 154 | break; | ||
| 155 | case "IgnoreUser": | ||
| 156 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 157 | { | ||
| 158 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_USER; | ||
| 159 | } | ||
| 160 | break; | ||
| 161 | case "IgnoreInfo": | ||
| 162 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 163 | { | ||
| 164 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INFO; | ||
| 165 | } | ||
| 166 | break; | ||
| 167 | case "IgnoreFilesInUse": | ||
| 168 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 169 | { | ||
| 170 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE; | ||
| 171 | } | ||
| 172 | break; | ||
| 173 | case "IgnoreResolveSource": | ||
| 174 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 175 | { | ||
| 176 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE; | ||
| 177 | } | ||
| 178 | break; | ||
| 179 | case "IgnoreOutOfDiskSpace": | ||
| 180 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 181 | { | ||
| 182 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE; | ||
| 183 | } | ||
| 184 | break; | ||
| 185 | case "IgnoreActionStart": | ||
| 186 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 187 | { | ||
| 188 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART; | ||
| 189 | } | ||
| 190 | break; | ||
| 191 | case "IgnoreActionData": | ||
| 192 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 193 | { | ||
| 194 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA; | ||
| 195 | } | ||
| 196 | break; | ||
| 197 | case "IgnoreProgress": | ||
| 198 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 199 | { | ||
| 200 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS; | ||
| 201 | } | ||
| 202 | break; | ||
| 203 | case "IgnoreCommonData": | ||
| 204 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 205 | { | ||
| 206 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA; | ||
| 207 | } | ||
| 208 | break; | ||
| 209 | case "IgnoreInitialize": | ||
| 210 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 211 | { | ||
| 212 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE; | ||
| 213 | } | ||
| 214 | break; | ||
| 215 | case "IgnoreTerminate": | ||
| 216 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 217 | { | ||
| 218 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE; | ||
| 219 | } | ||
| 220 | break; | ||
| 221 | case "IgnoreShowDialog": | ||
| 222 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 223 | { | ||
| 224 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG; | ||
| 225 | } | ||
| 226 | break; | ||
| 227 | case "IgnoreRMFilesInUse": | ||
| 228 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 229 | { | ||
| 230 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE; | ||
| 231 | } | ||
| 232 | break; | ||
| 233 | case "IgnoreInstallStart": | ||
| 234 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 235 | { | ||
| 236 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART; | ||
| 237 | } | ||
| 238 | break; | ||
| 239 | case "IgnoreInstallEnd": | ||
| 240 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 241 | { | ||
| 242 | messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; | ||
| 243 | } | ||
| 244 | break; | ||
| 245 | case "SourceFile": | ||
| 246 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 247 | break; | ||
| 248 | case "SupportBasicUI": | ||
| 249 | supportsBasicUI = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 250 | break; | ||
| 251 | default: | ||
| 252 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 253 | break; | ||
| 254 | } | ||
| 255 | } | ||
| 256 | else | ||
| 257 | { | ||
| 258 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | if (String.IsNullOrEmpty(sourceFile)) | ||
| 263 | { | ||
| 264 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
| 265 | } | ||
| 266 | else if (String.IsNullOrEmpty(name)) | ||
| 267 | { | ||
| 268 | name = Path.GetFileName(sourceFile); | ||
| 269 | if (!this.Core.IsValidLongFilename(name, false)) | ||
| 270 | { | ||
| 271 | this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | if (null == id) | ||
| 276 | { | ||
| 277 | if (!String.IsNullOrEmpty(name)) | ||
| 278 | { | ||
| 279 | id = this.Core.CreateIdentifierFromFilename(name); | ||
| 280 | } | ||
| 281 | |||
| 282 | if (null == id) | ||
| 283 | { | ||
| 284 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 285 | } | ||
| 286 | else if (!Common.IsIdentifier(id.Id)) | ||
| 287 | { | ||
| 288 | this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
| 289 | } | ||
| 290 | } | ||
| 291 | else if (String.IsNullOrEmpty(name)) | ||
| 292 | { | ||
| 293 | name = id.Id; | ||
| 294 | } | ||
| 295 | |||
| 296 | if (!name.Contains(".")) | ||
| 297 | { | ||
| 298 | this.Core.Write(ErrorMessages.InvalidEmbeddedUIFileName(sourceLineNumbers, name)); | ||
| 299 | } | ||
| 300 | |||
| 301 | foreach (var child in node.Elements()) | ||
| 302 | { | ||
| 303 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 304 | { | ||
| 305 | switch (child.Name.LocalName) | ||
| 306 | { | ||
| 307 | case "EmbeddedUIResource": | ||
| 308 | this.ParseEmbeddedUIResourceElement(child); | ||
| 309 | break; | ||
| 310 | default: | ||
| 311 | this.Core.UnexpectedElement(node, child); | ||
| 312 | break; | ||
| 313 | } | ||
| 314 | } | ||
| 315 | else | ||
| 316 | { | ||
| 317 | this.Core.ParseExtensionElement(node, child); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | if (!this.Core.EncounteredError) | ||
| 322 | { | ||
| 323 | this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) | ||
| 324 | { | ||
| 325 | FileName = name, | ||
| 326 | EntryPoint = true, | ||
| 327 | SupportsBasicUI = supportsBasicUI, | ||
| 328 | MessageFilter = messageFilter, | ||
| 329 | Source = sourceFile | ||
| 330 | }); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | /// <summary> | ||
| 335 | /// Parses a embedded UI resource element. | ||
| 336 | /// </summary> | ||
| 337 | /// <param name="node">Element to parse.</param> | ||
| 338 | private void ParseEmbeddedUIResourceElement(XElement node) | ||
| 339 | { | ||
| 340 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 341 | Identifier id = null; | ||
| 342 | string name = null; | ||
| 343 | string sourceFile = null; | ||
| 344 | |||
| 345 | foreach (var attrib in node.Attributes()) | ||
| 346 | { | ||
| 347 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 348 | { | ||
| 349 | switch (attrib.Name.LocalName) | ||
| 350 | { | ||
| 351 | case "Id": | ||
| 352 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 353 | break; | ||
| 354 | case "Name": | ||
| 355 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 356 | break; | ||
| 357 | case "SourceFile": | ||
| 358 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 359 | break; | ||
| 360 | default: | ||
| 361 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 362 | break; | ||
| 363 | } | ||
| 364 | } | ||
| 365 | else | ||
| 366 | { | ||
| 367 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 368 | } | ||
| 369 | } | ||
| 370 | |||
| 371 | if (String.IsNullOrEmpty(sourceFile)) | ||
| 372 | { | ||
| 373 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
| 374 | } | ||
| 375 | else if (String.IsNullOrEmpty(name)) | ||
| 376 | { | ||
| 377 | name = Path.GetFileName(sourceFile); | ||
| 378 | if (!this.Core.IsValidLongFilename(name, false)) | ||
| 379 | { | ||
| 380 | this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); | ||
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | if (null == id) | ||
| 385 | { | ||
| 386 | if (!String.IsNullOrEmpty(name)) | ||
| 387 | { | ||
| 388 | id = this.Core.CreateIdentifierFromFilename(name); | ||
| 389 | } | ||
| 390 | |||
| 391 | if (null == id) | ||
| 392 | { | ||
| 393 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 394 | } | ||
| 395 | else if (!Common.IsIdentifier(id.Id)) | ||
| 396 | { | ||
| 397 | this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); | ||
| 398 | } | ||
| 399 | } | ||
| 400 | else if (String.IsNullOrEmpty(name)) | ||
| 401 | { | ||
| 402 | name = id.Id; | ||
| 403 | } | ||
| 404 | |||
| 405 | this.Core.ParseForExtensionElements(node); | ||
| 406 | |||
| 407 | if (!this.Core.EncounteredError) | ||
| 408 | { | ||
| 409 | this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) | ||
| 410 | { | ||
| 411 | FileName = name, | ||
| 412 | Source = sourceFile | ||
| 413 | }); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | } | ||
| 417 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler_Module.cs b/src/wix/WixToolset.Core/Compiler_Module.cs new file mode 100644 index 00000000..3986c8da --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Module.cs | |||
| @@ -0,0 +1,662 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Globalization; | ||
| 7 | using System.Xml.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Compiler of the WiX toolset. | ||
| 14 | /// </summary> | ||
| 15 | internal partial class Compiler : ICompiler | ||
| 16 | { | ||
| 17 | /// <summary> | ||
| 18 | /// Parses a module element. | ||
| 19 | /// </summary> | ||
| 20 | /// <param name="node">Element to parse.</param> | ||
| 21 | private void ParseModuleElement(XElement node) | ||
| 22 | { | ||
| 23 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 24 | var codepage = 0; | ||
| 25 | string moduleId = null; | ||
| 26 | string version = null; | ||
| 27 | var setCodepage = false; | ||
| 28 | var setPackageName = false; | ||
| 29 | var setKeywords = false; | ||
| 30 | var ignoredForMergeModules = false; | ||
| 31 | |||
| 32 | this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); | ||
| 33 | |||
| 34 | this.activeName = null; | ||
| 35 | this.activeLanguage = null; | ||
| 36 | |||
| 37 | foreach (var attrib in node.Attributes()) | ||
| 38 | { | ||
| 39 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 40 | { | ||
| 41 | switch (attrib.Name.LocalName) | ||
| 42 | { | ||
| 43 | case "Id": | ||
| 44 | this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 45 | if ("PUT-MODULE-NAME-HERE" == this.activeName) | ||
| 46 | { | ||
| 47 | this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); | ||
| 48 | } | ||
| 49 | else | ||
| 50 | { | ||
| 51 | this.activeName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 52 | } | ||
| 53 | break; | ||
| 54 | case "Codepage": | ||
| 55 | codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); | ||
| 56 | break; | ||
| 57 | case "Guid": | ||
| 58 | moduleId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 59 | break; | ||
| 60 | case "InstallerVersion": | ||
| 61 | msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 62 | break; | ||
| 63 | case "Language": | ||
| 64 | this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 65 | break; | ||
| 66 | case "Version": | ||
| 67 | version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 68 | break; | ||
| 69 | default: | ||
| 70 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 71 | break; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | else | ||
| 75 | { | ||
| 76 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | if (null == this.activeName) | ||
| 81 | { | ||
| 82 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 83 | } | ||
| 84 | |||
| 85 | if (null == moduleId) | ||
| 86 | { | ||
| 87 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Guid")); | ||
| 88 | } | ||
| 89 | |||
| 90 | if (null == this.activeLanguage) | ||
| 91 | { | ||
| 92 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); | ||
| 93 | } | ||
| 94 | |||
| 95 | if (null == version) | ||
| 96 | { | ||
| 97 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
| 98 | } | ||
| 99 | else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) | ||
| 100 | { | ||
| 101 | this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Module", version)); | ||
| 102 | } | ||
| 103 | |||
| 104 | try | ||
| 105 | { | ||
| 106 | this.compilingModule = true; // notice that we are actually building a Merge Module here | ||
| 107 | this.Core.CreateActiveSection(this.activeName, SectionType.Module, this.Context.CompilationId); | ||
| 108 | |||
| 109 | foreach (var child in node.Elements()) | ||
| 110 | { | ||
| 111 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 112 | { | ||
| 113 | switch (child.Name.LocalName) | ||
| 114 | { | ||
| 115 | case "AdminExecuteSequence": | ||
| 116 | this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); | ||
| 117 | break; | ||
| 118 | case "AdminUISequence": | ||
| 119 | this.ParseSequenceElement(child, SequenceTable.AdminUISequence); | ||
| 120 | break; | ||
| 121 | case "AdvertiseExecuteSequence": | ||
| 122 | this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); | ||
| 123 | break; | ||
| 124 | case "InstallExecuteSequence": | ||
| 125 | this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); | ||
| 126 | break; | ||
| 127 | case "InstallUISequence": | ||
| 128 | this.ParseSequenceElement(child, SequenceTable.InstallUISequence); | ||
| 129 | break; | ||
| 130 | case "AppId": | ||
| 131 | this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); | ||
| 132 | break; | ||
| 133 | case "Binary": | ||
| 134 | this.ParseBinaryElement(child); | ||
| 135 | break; | ||
| 136 | case "Component": | ||
| 137 | this.ParseComponentElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, CompilerConstants.IntegerNotSet, null, null); | ||
| 138 | break; | ||
| 139 | case "ComponentGroupRef": | ||
| 140 | this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); | ||
| 141 | break; | ||
| 142 | case "ComponentRef": | ||
| 143 | this.ParseComponentRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); | ||
| 144 | break; | ||
| 145 | case "Configuration": | ||
| 146 | this.ParseConfigurationElement(child); | ||
| 147 | break; | ||
| 148 | case "CustomAction": | ||
| 149 | this.ParseCustomActionElement(child); | ||
| 150 | break; | ||
| 151 | case "CustomActionRef": | ||
| 152 | this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); | ||
| 153 | break; | ||
| 154 | case "CustomTable": | ||
| 155 | this.ParseCustomTableElement(child); | ||
| 156 | break; | ||
| 157 | case "CustomTableRef": | ||
| 158 | this.ParseCustomTableRefElement(child); | ||
| 159 | break; | ||
| 160 | case "Dependency": | ||
| 161 | this.ParseDependencyElement(child); | ||
| 162 | break; | ||
| 163 | case "Directory": | ||
| 164 | this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); | ||
| 165 | break; | ||
| 166 | case "DirectoryRef": | ||
| 167 | this.ParseDirectoryRefElement(child); | ||
| 168 | break; | ||
| 169 | case "EmbeddedChainer": | ||
| 170 | this.ParseEmbeddedChainerElement(child); | ||
| 171 | break; | ||
| 172 | case "EmbeddedChainerRef": | ||
| 173 | this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); | ||
| 174 | break; | ||
| 175 | case "EnsureTable": | ||
| 176 | this.ParseEnsureTableElement(child); | ||
| 177 | break; | ||
| 178 | case "Exclusion": | ||
| 179 | this.ParseExclusionElement(child); | ||
| 180 | break; | ||
| 181 | case "Icon": | ||
| 182 | this.ParseIconElement(child); | ||
| 183 | break; | ||
| 184 | case "IgnoreTable": | ||
| 185 | this.ParseIgnoreTableElement(child); | ||
| 186 | break; | ||
| 187 | case "Property": | ||
| 188 | this.ParsePropertyElement(child); | ||
| 189 | break; | ||
| 190 | case "PropertyRef": | ||
| 191 | this.ParseSimpleRefElement(child, SymbolDefinitions.Property); | ||
| 192 | break; | ||
| 193 | case "Requires": | ||
| 194 | this.ParseRequiresElement(child, null); | ||
| 195 | break; | ||
| 196 | case "SetDirectory": | ||
| 197 | this.ParseSetDirectoryElement(child); | ||
| 198 | break; | ||
| 199 | case "SetProperty": | ||
| 200 | this.ParseSetPropertyElement(child); | ||
| 201 | break; | ||
| 202 | case "SFPCatalog": | ||
| 203 | string parentName = null; | ||
| 204 | this.ParseSFPCatalogElement(child, ref parentName); | ||
| 205 | break; | ||
| 206 | case "StandardDirectory": | ||
| 207 | this.ParseStandardDirectoryElement(child); | ||
| 208 | break; | ||
| 209 | case "Substitution": | ||
| 210 | this.ParseSubstitutionElement(child); | ||
| 211 | break; | ||
| 212 | case "SummaryInformation": | ||
| 213 | this.ParseSummaryInformationElement(child, ref setCodepage, ref setPackageName, ref setKeywords, ref ignoredForMergeModules); | ||
| 214 | break; | ||
| 215 | case "UI": | ||
| 216 | this.ParseUIElement(child); | ||
| 217 | break; | ||
| 218 | case "UIRef": | ||
| 219 | this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); | ||
| 220 | break; | ||
| 221 | case "WixVariable": | ||
| 222 | this.ParseWixVariableElement(child); | ||
| 223 | break; | ||
| 224 | default: | ||
| 225 | this.Core.UnexpectedElement(node, child); | ||
| 226 | break; | ||
| 227 | } | ||
| 228 | } | ||
| 229 | else | ||
| 230 | { | ||
| 231 | this.Core.ParseExtensionElement(node, child); | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | |||
| 236 | if (!this.Core.EncounteredError) | ||
| 237 | { | ||
| 238 | if (!setPackageName) | ||
| 239 | { | ||
| 240 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 241 | { | ||
| 242 | PropertyId = SummaryInformationType.Subject, | ||
| 243 | Value = this.activeName | ||
| 244 | }); | ||
| 245 | } | ||
| 246 | |||
| 247 | if (!setKeywords) | ||
| 248 | { | ||
| 249 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 250 | { | ||
| 251 | PropertyId = SummaryInformationType.Keywords, | ||
| 252 | Value = "Installer" | ||
| 253 | }); | ||
| 254 | } | ||
| 255 | |||
| 256 | var symbol = this.Core.AddSymbol(new WixModuleSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, this.activeName, this.activeLanguage)) | ||
| 257 | { | ||
| 258 | ModuleId = this.activeName, | ||
| 259 | Language = this.activeLanguage, | ||
| 260 | Version = version | ||
| 261 | }); | ||
| 262 | |||
| 263 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 264 | { | ||
| 265 | PropertyId = SummaryInformationType.PackageCode, | ||
| 266 | Value = moduleId | ||
| 267 | }); | ||
| 268 | |||
| 269 | this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, this.activeLanguage); | ||
| 270 | } | ||
| 271 | } | ||
| 272 | finally | ||
| 273 | { | ||
| 274 | this.compilingModule = false; // notice that we are no longer building a Merge Module here | ||
| 275 | } | ||
| 276 | } | ||
| 277 | |||
| 278 | /// <summary> | ||
| 279 | /// Parses a dependency element. | ||
| 280 | /// </summary> | ||
| 281 | /// <param name="node">Element to parse.</param> | ||
| 282 | private void ParseDependencyElement(XElement node) | ||
| 283 | { | ||
| 284 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 285 | string requiredId = null; | ||
| 286 | var requiredLanguage = CompilerConstants.IntegerNotSet; | ||
| 287 | string requiredVersion = null; | ||
| 288 | |||
| 289 | foreach (var attrib in node.Attributes()) | ||
| 290 | { | ||
| 291 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 292 | { | ||
| 293 | switch (attrib.Name.LocalName) | ||
| 294 | { | ||
| 295 | case "RequiredId": | ||
| 296 | requiredId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 297 | break; | ||
| 298 | case "RequiredLanguage": | ||
| 299 | requiredLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 300 | break; | ||
| 301 | case "RequiredVersion": | ||
| 302 | requiredVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 303 | break; | ||
| 304 | default: | ||
| 305 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 306 | break; | ||
| 307 | } | ||
| 308 | } | ||
| 309 | else | ||
| 310 | { | ||
| 311 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | if (null == requiredId) | ||
| 316 | { | ||
| 317 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredId")); | ||
| 318 | requiredId = String.Empty; | ||
| 319 | } | ||
| 320 | |||
| 321 | if (CompilerConstants.IntegerNotSet == requiredLanguage) | ||
| 322 | { | ||
| 323 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredLanguage")); | ||
| 324 | requiredLanguage = CompilerConstants.IllegalInteger; | ||
| 325 | } | ||
| 326 | |||
| 327 | this.Core.ParseForExtensionElements(node); | ||
| 328 | |||
| 329 | if (!this.Core.EncounteredError) | ||
| 330 | { | ||
| 331 | var symbol = this.Core.AddSymbol(new ModuleDependencySymbol(sourceLineNumbers) | ||
| 332 | { | ||
| 333 | ModuleID = this.activeName, | ||
| 334 | RequiredID = requiredId, | ||
| 335 | RequiredLanguage = requiredLanguage, | ||
| 336 | RequiredVersion = requiredVersion | ||
| 337 | }); | ||
| 338 | |||
| 339 | symbol.Set((int)ModuleDependencySymbolFields.ModuleLanguage, this.activeLanguage); | ||
| 340 | } | ||
| 341 | } | ||
| 342 | |||
| 343 | /// <summary> | ||
| 344 | /// Parses an exclusion element. | ||
| 345 | /// </summary> | ||
| 346 | /// <param name="node">Element to parse.</param> | ||
| 347 | private void ParseExclusionElement(XElement node) | ||
| 348 | { | ||
| 349 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 350 | string excludedId = null; | ||
| 351 | var excludeExceptLanguage = CompilerConstants.IntegerNotSet; | ||
| 352 | var excludeLanguage = CompilerConstants.IntegerNotSet; | ||
| 353 | var excludedLanguageField = "0"; | ||
| 354 | string excludedMaxVersion = null; | ||
| 355 | string excludedMinVersion = null; | ||
| 356 | |||
| 357 | foreach (var attrib in node.Attributes()) | ||
| 358 | { | ||
| 359 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 360 | { | ||
| 361 | switch (attrib.Name.LocalName) | ||
| 362 | { | ||
| 363 | case "ExcludedId": | ||
| 364 | excludedId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 365 | break; | ||
| 366 | case "ExcludeExceptLanguage": | ||
| 367 | excludeExceptLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 368 | break; | ||
| 369 | case "ExcludeLanguage": | ||
| 370 | excludeLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 371 | break; | ||
| 372 | case "ExcludedMaxVersion": | ||
| 373 | excludedMaxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 374 | break; | ||
| 375 | case "ExcludedMinVersion": | ||
| 376 | excludedMinVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 377 | break; | ||
| 378 | default: | ||
| 379 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 380 | break; | ||
| 381 | } | ||
| 382 | } | ||
| 383 | else | ||
| 384 | { | ||
| 385 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 386 | } | ||
| 387 | } | ||
| 388 | |||
| 389 | if (null == excludedId) | ||
| 390 | { | ||
| 391 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExcludedId")); | ||
| 392 | excludedId = String.Empty; | ||
| 393 | } | ||
| 394 | |||
| 395 | if (CompilerConstants.IntegerNotSet != excludeExceptLanguage && CompilerConstants.IntegerNotSet != excludeLanguage) | ||
| 396 | { | ||
| 397 | this.Core.Write(ErrorMessages.IllegalModuleExclusionLanguageAttributes(sourceLineNumbers)); | ||
| 398 | } | ||
| 399 | else if (CompilerConstants.IntegerNotSet != excludeExceptLanguage) | ||
| 400 | { | ||
| 401 | excludedLanguageField = Convert.ToString(-excludeExceptLanguage, CultureInfo.InvariantCulture); | ||
| 402 | } | ||
| 403 | else if (CompilerConstants.IntegerNotSet != excludeLanguage) | ||
| 404 | { | ||
| 405 | excludedLanguageField = Convert.ToString(excludeLanguage, CultureInfo.InvariantCulture); | ||
| 406 | } | ||
| 407 | |||
| 408 | this.Core.ParseForExtensionElements(node); | ||
| 409 | |||
| 410 | if (!this.Core.EncounteredError) | ||
| 411 | { | ||
| 412 | var symbol = this.Core.AddSymbol(new ModuleExclusionSymbol(sourceLineNumbers) | ||
| 413 | { | ||
| 414 | ModuleID = this.activeName, | ||
| 415 | ExcludedID = excludedId, | ||
| 416 | ExcludedMinVersion = excludedMinVersion, | ||
| 417 | ExcludedMaxVersion = excludedMaxVersion | ||
| 418 | }); | ||
| 419 | |||
| 420 | symbol.Set((int)ModuleExclusionSymbolFields.ModuleLanguage, this.activeLanguage); | ||
| 421 | symbol.Set((int)ModuleExclusionSymbolFields.ExcludedLanguage, excludedLanguageField); | ||
| 422 | } | ||
| 423 | } | ||
| 424 | |||
| 425 | /// <summary> | ||
| 426 | /// Parses a configuration element for a configurable merge module. | ||
| 427 | /// </summary> | ||
| 428 | /// <param name="node">Element to parse.</param> | ||
| 429 | private void ParseConfigurationElement(XElement node) | ||
| 430 | { | ||
| 431 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 432 | string contextData = null; | ||
| 433 | string defaultValue = null; | ||
| 434 | string description = null; | ||
| 435 | string displayName = null; | ||
| 436 | var format = CompilerConstants.IntegerNotSet; | ||
| 437 | string helpKeyword = null; | ||
| 438 | string helpLocation = null; | ||
| 439 | bool keyNoOrphan = false; | ||
| 440 | bool nonNullable = false; | ||
| 441 | Identifier name = null; | ||
| 442 | string type = null; | ||
| 443 | |||
| 444 | foreach (var attrib in node.Attributes()) | ||
| 445 | { | ||
| 446 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 447 | { | ||
| 448 | switch (attrib.Name.LocalName) | ||
| 449 | { | ||
| 450 | case "Name": | ||
| 451 | name = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 452 | break; | ||
| 453 | case "ContextData": | ||
| 454 | contextData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 455 | break; | ||
| 456 | case "Description": | ||
| 457 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 458 | break; | ||
| 459 | case "DefaultValue": | ||
| 460 | defaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 461 | break; | ||
| 462 | case "DisplayName": | ||
| 463 | displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 464 | break; | ||
| 465 | case "Format": | ||
| 466 | var formatStr = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 467 | switch (formatStr) | ||
| 468 | { | ||
| 469 | case "Text": | ||
| 470 | case "text": | ||
| 471 | format = 0; | ||
| 472 | break; | ||
| 473 | case "Key": | ||
| 474 | case "key": | ||
| 475 | format = 1; | ||
| 476 | break; | ||
| 477 | case "Integer": | ||
| 478 | case "integer": | ||
| 479 | format = 2; | ||
| 480 | break; | ||
| 481 | case "Bitfield": | ||
| 482 | case "bitfield": | ||
| 483 | format = 3; | ||
| 484 | break; | ||
| 485 | case "": | ||
| 486 | break; | ||
| 487 | default: | ||
| 488 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Format", formatStr, "Text", "Key", "Integer", "Bitfield")); | ||
| 489 | break; | ||
| 490 | } | ||
| 491 | break; | ||
| 492 | case "HelpKeyword": | ||
| 493 | helpKeyword = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 494 | break; | ||
| 495 | case "HelpLocation": | ||
| 496 | helpLocation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 497 | break; | ||
| 498 | case "KeyNoOrphan": | ||
| 499 | keyNoOrphan = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 500 | break; | ||
| 501 | case "NonNullable": | ||
| 502 | nonNullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 503 | break; | ||
| 504 | case "Type": | ||
| 505 | type = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 506 | break; | ||
| 507 | default: | ||
| 508 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 509 | break; | ||
| 510 | } | ||
| 511 | } | ||
| 512 | else | ||
| 513 | { | ||
| 514 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 515 | } | ||
| 516 | } | ||
| 517 | |||
| 518 | if (null == name) | ||
| 519 | { | ||
| 520 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 521 | } | ||
| 522 | |||
| 523 | if (CompilerConstants.IntegerNotSet == format) | ||
| 524 | { | ||
| 525 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Format")); | ||
| 526 | } | ||
| 527 | |||
| 528 | this.Core.ParseForExtensionElements(node); | ||
| 529 | |||
| 530 | if (!this.Core.EncounteredError) | ||
| 531 | { | ||
| 532 | this.Core.AddSymbol(new ModuleConfigurationSymbol(sourceLineNumbers, name) | ||
| 533 | { | ||
| 534 | Format = format, | ||
| 535 | Type = type, | ||
| 536 | ContextData = contextData, | ||
| 537 | DefaultValue = defaultValue, | ||
| 538 | KeyNoOrphan = keyNoOrphan, | ||
| 539 | NonNullable = nonNullable, | ||
| 540 | DisplayName = displayName, | ||
| 541 | Description = description, | ||
| 542 | HelpLocation = helpLocation, | ||
| 543 | HelpKeyword = helpKeyword | ||
| 544 | }); | ||
| 545 | } | ||
| 546 | } | ||
| 547 | |||
| 548 | /// <summary> | ||
| 549 | /// Parses a substitution element for a configurable merge module. | ||
| 550 | /// </summary> | ||
| 551 | /// <param name="node">Element to parse.</param> | ||
| 552 | private void ParseSubstitutionElement(XElement node) | ||
| 553 | { | ||
| 554 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 555 | string column = null; | ||
| 556 | string rowKeys = null; | ||
| 557 | string table = null; | ||
| 558 | string value = null; | ||
| 559 | |||
| 560 | foreach (var attrib in node.Attributes()) | ||
| 561 | { | ||
| 562 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 563 | { | ||
| 564 | switch (attrib.Name.LocalName) | ||
| 565 | { | ||
| 566 | case "Column": | ||
| 567 | column = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 568 | break; | ||
| 569 | case "Row": | ||
| 570 | rowKeys = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 571 | break; | ||
| 572 | case "Table": | ||
| 573 | table = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 574 | break; | ||
| 575 | case "Value": | ||
| 576 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 577 | break; | ||
| 578 | default: | ||
| 579 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 580 | break; | ||
| 581 | } | ||
| 582 | } | ||
| 583 | else | ||
| 584 | { | ||
| 585 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 586 | } | ||
| 587 | } | ||
| 588 | |||
| 589 | if (null == column) | ||
| 590 | { | ||
| 591 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Column")); | ||
| 592 | column = String.Empty; | ||
| 593 | } | ||
| 594 | |||
| 595 | if (null == table) | ||
| 596 | { | ||
| 597 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Table")); | ||
| 598 | table = String.Empty; | ||
| 599 | } | ||
| 600 | |||
| 601 | if (null == rowKeys) | ||
| 602 | { | ||
| 603 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Row")); | ||
| 604 | } | ||
| 605 | |||
| 606 | this.Core.ParseForExtensionElements(node); | ||
| 607 | |||
| 608 | if (!this.Core.EncounteredError) | ||
| 609 | { | ||
| 610 | this.Core.AddSymbol(new ModuleSubstitutionSymbol(sourceLineNumbers) | ||
| 611 | { | ||
| 612 | Table = table, | ||
| 613 | Row = rowKeys, | ||
| 614 | Column = column, | ||
| 615 | Value = value | ||
| 616 | }); | ||
| 617 | } | ||
| 618 | } | ||
| 619 | |||
| 620 | /// <summary> | ||
| 621 | /// Parses an IgnoreTable element. | ||
| 622 | /// </summary> | ||
| 623 | /// <param name="node">Element to parse.</param> | ||
| 624 | private void ParseIgnoreTableElement(XElement node) | ||
| 625 | { | ||
| 626 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 627 | string id = null; | ||
| 628 | |||
| 629 | foreach (var attrib in node.Attributes()) | ||
| 630 | { | ||
| 631 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 632 | { | ||
| 633 | switch (attrib.Name.LocalName) | ||
| 634 | { | ||
| 635 | case "Id": | ||
| 636 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 637 | break; | ||
| 638 | default: | ||
| 639 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 640 | break; | ||
| 641 | } | ||
| 642 | } | ||
| 643 | else | ||
| 644 | { | ||
| 645 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 646 | } | ||
| 647 | } | ||
| 648 | |||
| 649 | if (null == id) | ||
| 650 | { | ||
| 651 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 652 | } | ||
| 653 | |||
| 654 | this.Core.ParseForExtensionElements(node); | ||
| 655 | |||
| 656 | if (!this.Core.EncounteredError) | ||
| 657 | { | ||
| 658 | this.Core.AddSymbol(new ModuleIgnoreTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, id))); | ||
| 659 | } | ||
| 660 | } | ||
| 661 | } | ||
| 662 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler_Package.cs b/src/wix/WixToolset.Core/Compiler_Package.cs new file mode 100644 index 00000000..87ccceb7 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Package.cs | |||
| @@ -0,0 +1,4996 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Xml.Linq; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Data.WindowsInstaller; | ||
| 14 | using WixToolset.Extensibility; | ||
| 15 | |||
| 16 | /// <summary> | ||
| 17 | /// Compiler of the WiX toolset. | ||
| 18 | /// </summary> | ||
| 19 | internal partial class Compiler : ICompiler | ||
| 20 | { | ||
| 21 | /// <summary> | ||
| 22 | /// Parses a product element. | ||
| 23 | /// </summary> | ||
| 24 | /// <param name="node">Element to parse.</param> | ||
| 25 | private void ParsePackageElement(XElement node) | ||
| 26 | { | ||
| 27 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 28 | var compressed = YesNoDefaultType.Default; | ||
| 29 | var sourceBits = 0; | ||
| 30 | string codepage = null; | ||
| 31 | var productCode = "*"; | ||
| 32 | string productLanguage = null; | ||
| 33 | var isPerMachine = true; | ||
| 34 | string upgradeCode = null; | ||
| 35 | string manufacturer = null; | ||
| 36 | string version = null; | ||
| 37 | string symbols = null; | ||
| 38 | var isCodepageSet = false; | ||
| 39 | var isPackageNameSet = false; | ||
| 40 | var isKeywordsSet = false; | ||
| 41 | var isPackageAuthorSet = false; | ||
| 42 | |||
| 43 | this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); | ||
| 44 | |||
| 45 | this.activeName = null; | ||
| 46 | this.activeLanguage = null; | ||
| 47 | |||
| 48 | foreach (var attrib in node.Attributes()) | ||
| 49 | { | ||
| 50 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 51 | { | ||
| 52 | switch (attrib.Name.LocalName) | ||
| 53 | { | ||
| 54 | case "Codepage": | ||
| 55 | codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); | ||
| 56 | break; | ||
| 57 | case "Compressed": | ||
| 58 | compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
| 59 | break; | ||
| 60 | case "InstallerVersion": | ||
| 61 | msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 62 | break; | ||
| 63 | case "Language": | ||
| 64 | productLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 65 | break; | ||
| 66 | case "Manufacturer": | ||
| 67 | manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); | ||
| 68 | if ("PUT-COMPANY-NAME-HERE" == manufacturer) | ||
| 69 | { | ||
| 70 | this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer)); | ||
| 71 | } | ||
| 72 | break; | ||
| 73 | case "Name": | ||
| 74 | this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); | ||
| 75 | if ("PUT-PRODUCT-NAME-HERE" == this.activeName) | ||
| 76 | { | ||
| 77 | this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); | ||
| 78 | } | ||
| 79 | break; | ||
| 80 | case "ProductCode": | ||
| 81 | productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); | ||
| 82 | break; | ||
| 83 | case "Scope": | ||
| 84 | var installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 85 | switch (installScope) | ||
| 86 | { | ||
| 87 | case "perMachine": | ||
| 88 | // handled below after we create the section. | ||
| 89 | break; | ||
| 90 | case "perUser": | ||
| 91 | isPerMachine = false; | ||
| 92 | sourceBits |= 8; | ||
| 93 | break; | ||
| 94 | default: | ||
| 95 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); | ||
| 96 | break; | ||
| 97 | } | ||
| 98 | break; | ||
| 99 | case "ShortNames": | ||
| 100 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 101 | { | ||
| 102 | sourceBits |= 1; | ||
| 103 | } | ||
| 104 | break; | ||
| 105 | case "UpgradeCode": | ||
| 106 | upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 107 | break; | ||
| 108 | case "Version": // if the attribute is valid version, use the attribute value as is (so "1.0000.01.01" would *not* get translated to "1.0.1.1"). | ||
| 109 | var verifiedVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 110 | if (!String.IsNullOrEmpty(verifiedVersion)) | ||
| 111 | { | ||
| 112 | version = attrib.Value; | ||
| 113 | } | ||
| 114 | break; | ||
| 115 | default: | ||
| 116 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 117 | break; | ||
| 118 | } | ||
| 119 | } | ||
| 120 | else | ||
| 121 | { | ||
| 122 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | if (null == productCode) | ||
| 127 | { | ||
| 128 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 129 | } | ||
| 130 | |||
| 131 | if (null == manufacturer) | ||
| 132 | { | ||
| 133 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); | ||
| 134 | } | ||
| 135 | |||
| 136 | if (null == this.activeName) | ||
| 137 | { | ||
| 138 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 139 | } | ||
| 140 | |||
| 141 | if (null == upgradeCode) | ||
| 142 | { | ||
| 143 | this.Core.Write(WarningMessages.MissingUpgradeCode(sourceLineNumbers)); | ||
| 144 | } | ||
| 145 | |||
| 146 | if (null == version) | ||
| 147 | { | ||
| 148 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
| 149 | } | ||
| 150 | else if (!CompilerCore.IsValidProductVersion(version)) | ||
| 151 | { | ||
| 152 | this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); | ||
| 153 | } | ||
| 154 | |||
| 155 | if (compressed != YesNoDefaultType.No) | ||
| 156 | { | ||
| 157 | sourceBits |= 2; | ||
| 158 | } | ||
| 159 | |||
| 160 | if (this.Core.EncounteredError) | ||
| 161 | { | ||
| 162 | return; | ||
| 163 | } | ||
| 164 | |||
| 165 | try | ||
| 166 | { | ||
| 167 | this.compilingProduct = true; | ||
| 168 | this.Core.CreateActiveSection(productCode, SectionType.Product, this.Context.CompilationId); | ||
| 169 | |||
| 170 | this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); | ||
| 171 | this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); | ||
| 172 | this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); | ||
| 173 | this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); | ||
| 174 | this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); | ||
| 175 | if (null != upgradeCode) | ||
| 176 | { | ||
| 177 | this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); | ||
| 178 | } | ||
| 179 | |||
| 180 | if (isPerMachine) | ||
| 181 | { | ||
| 182 | this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); | ||
| 183 | } | ||
| 184 | |||
| 185 | this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, productLanguage); | ||
| 186 | |||
| 187 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 188 | { | ||
| 189 | PropertyId = SummaryInformationType.WordCount, | ||
| 190 | Value = sourceBits.ToString(CultureInfo.InvariantCulture) | ||
| 191 | }); | ||
| 192 | |||
| 193 | var contextValues = new Dictionary<string, string> | ||
| 194 | { | ||
| 195 | ["ProductLanguage"] = productLanguage, | ||
| 196 | ["ProductVersion"] = version, | ||
| 197 | ["UpgradeCode"] = upgradeCode | ||
| 198 | }; | ||
| 199 | |||
| 200 | var featureDisplay = 0; | ||
| 201 | foreach (var child in node.Elements()) | ||
| 202 | { | ||
| 203 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 204 | { | ||
| 205 | switch (child.Name.LocalName) | ||
| 206 | { | ||
| 207 | case "_locDefinition": | ||
| 208 | break; | ||
| 209 | case "AdminExecuteSequence": | ||
| 210 | this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); | ||
| 211 | break; | ||
| 212 | case "AdminUISequence": | ||
| 213 | this.ParseSequenceElement(child, SequenceTable.AdminUISequence); | ||
| 214 | break; | ||
| 215 | case "AdvertiseExecuteSequence": | ||
| 216 | this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); | ||
| 217 | break; | ||
| 218 | case "InstallExecuteSequence": | ||
| 219 | this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); | ||
| 220 | break; | ||
| 221 | case "InstallUISequence": | ||
| 222 | this.ParseSequenceElement(child, SequenceTable.InstallUISequence); | ||
| 223 | break; | ||
| 224 | case "AppId": | ||
| 225 | this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); | ||
| 226 | break; | ||
| 227 | case "Binary": | ||
| 228 | this.ParseBinaryElement(child); | ||
| 229 | break; | ||
| 230 | case "ComplianceCheck": | ||
| 231 | this.ParseComplianceCheckElement(child); | ||
| 232 | break; | ||
| 233 | case "Component": | ||
| 234 | this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); | ||
| 235 | break; | ||
| 236 | case "ComponentGroup": | ||
| 237 | this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); | ||
| 238 | break; | ||
| 239 | case "CustomAction": | ||
| 240 | this.ParseCustomActionElement(child); | ||
| 241 | break; | ||
| 242 | case "CustomActionRef": | ||
| 243 | this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); | ||
| 244 | break; | ||
| 245 | case "CustomTable": | ||
| 246 | this.ParseCustomTableElement(child); | ||
| 247 | break; | ||
| 248 | case "CustomTableRef": | ||
| 249 | this.ParseCustomTableRefElement(child); | ||
| 250 | break; | ||
| 251 | case "Directory": | ||
| 252 | this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); | ||
| 253 | break; | ||
| 254 | case "DirectoryRef": | ||
| 255 | this.ParseDirectoryRefElement(child); | ||
| 256 | break; | ||
| 257 | case "EmbeddedChainer": | ||
| 258 | this.ParseEmbeddedChainerElement(child); | ||
| 259 | break; | ||
| 260 | case "EmbeddedChainerRef": | ||
| 261 | this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); | ||
| 262 | break; | ||
| 263 | case "EnsureTable": | ||
| 264 | this.ParseEnsureTableElement(child); | ||
| 265 | break; | ||
| 266 | case "Feature": | ||
| 267 | this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay); | ||
| 268 | break; | ||
| 269 | case "FeatureRef": | ||
| 270 | this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode); | ||
| 271 | break; | ||
| 272 | case "FeatureGroupRef": | ||
| 273 | this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); | ||
| 274 | break; | ||
| 275 | case "Icon": | ||
| 276 | this.ParseIconElement(child); | ||
| 277 | break; | ||
| 278 | case "InstanceTransforms": | ||
| 279 | this.ParseInstanceTransformsElement(child); | ||
| 280 | break; | ||
| 281 | case "Launch": | ||
| 282 | this.ParseLaunchElement(child); | ||
| 283 | break; | ||
| 284 | case "MajorUpgrade": | ||
| 285 | this.ParseMajorUpgradeElement(child, contextValues); | ||
| 286 | break; | ||
| 287 | case "Media": | ||
| 288 | this.ParseMediaElement(child, null); | ||
| 289 | break; | ||
| 290 | case "MediaTemplate": | ||
| 291 | this.ParseMediaTemplateElement(child, null); | ||
| 292 | break; | ||
| 293 | case "PackageCertificates": | ||
| 294 | case "PatchCertificates": | ||
| 295 | this.ParseCertificatesElement(child); | ||
| 296 | break; | ||
| 297 | case "Property": | ||
| 298 | this.ParsePropertyElement(child); | ||
| 299 | break; | ||
| 300 | case "PropertyRef": | ||
| 301 | this.ParseSimpleRefElement(child, SymbolDefinitions.Property); | ||
| 302 | break; | ||
| 303 | case "Requires": | ||
| 304 | this.ParseRequiresElement(child, null); | ||
| 305 | break; | ||
| 306 | case "SetDirectory": | ||
| 307 | this.ParseSetDirectoryElement(child); | ||
| 308 | break; | ||
| 309 | case "SetProperty": | ||
| 310 | this.ParseSetPropertyElement(child); | ||
| 311 | break; | ||
| 312 | case "SFPCatalog": | ||
| 313 | string parentName = null; | ||
| 314 | this.ParseSFPCatalogElement(child, ref parentName); | ||
| 315 | break; | ||
| 316 | case "SoftwareTag": | ||
| 317 | this.ParsePackageTagElement(child); | ||
| 318 | break; | ||
| 319 | case "StandardDirectory": | ||
| 320 | this.ParseStandardDirectoryElement(child); | ||
| 321 | break; | ||
| 322 | case "SummaryInformation": | ||
| 323 | this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); | ||
| 324 | break; | ||
| 325 | case "SymbolPath": | ||
| 326 | if (null != symbols) | ||
| 327 | { | ||
| 328 | symbols += ";" + this.ParseSymbolPathElement(child); | ||
| 329 | } | ||
| 330 | else | ||
| 331 | { | ||
| 332 | symbols = this.ParseSymbolPathElement(child); | ||
| 333 | } | ||
| 334 | break; | ||
| 335 | case "UI": | ||
| 336 | this.ParseUIElement(child); | ||
| 337 | break; | ||
| 338 | case "UIRef": | ||
| 339 | this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); | ||
| 340 | break; | ||
| 341 | case "Upgrade": | ||
| 342 | this.ParseUpgradeElement(child); | ||
| 343 | break; | ||
| 344 | case "WixVariable": | ||
| 345 | this.ParseWixVariableElement(child); | ||
| 346 | break; | ||
| 347 | default: | ||
| 348 | this.Core.UnexpectedElement(node, child); | ||
| 349 | break; | ||
| 350 | } | ||
| 351 | } | ||
| 352 | else | ||
| 353 | { | ||
| 354 | this.Core.ParseExtensionElement(node, child); | ||
| 355 | } | ||
| 356 | } | ||
| 357 | |||
| 358 | if (!this.Core.EncounteredError) | ||
| 359 | { | ||
| 360 | this.Core.AddSymbol(new WixPackageSymbol(sourceLineNumbers) | ||
| 361 | { | ||
| 362 | PackageId = productCode, | ||
| 363 | UpgradeCode = upgradeCode, | ||
| 364 | Name = this.activeName, | ||
| 365 | Language = productLanguage, | ||
| 366 | Version = version, | ||
| 367 | Manufacturer = manufacturer, | ||
| 368 | Attributes = isPerMachine ? WixPackageAttributes.PerMachine : WixPackageAttributes.None, | ||
| 369 | Codepage = codepage, | ||
| 370 | }); | ||
| 371 | |||
| 372 | if (!isPackageNameSet) | ||
| 373 | { | ||
| 374 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 375 | { | ||
| 376 | PropertyId = SummaryInformationType.Subject, | ||
| 377 | Value = this.activeName | ||
| 378 | }); | ||
| 379 | } | ||
| 380 | |||
| 381 | if (!isPackageAuthorSet) | ||
| 382 | { | ||
| 383 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 384 | { | ||
| 385 | PropertyId = SummaryInformationType.Author, | ||
| 386 | Value = manufacturer | ||
| 387 | }); | ||
| 388 | } | ||
| 389 | |||
| 390 | if (!isKeywordsSet) | ||
| 391 | { | ||
| 392 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 393 | { | ||
| 394 | PropertyId = SummaryInformationType.Keywords, | ||
| 395 | Value = "Installer" | ||
| 396 | }); | ||
| 397 | } | ||
| 398 | |||
| 399 | if (null != symbols) | ||
| 400 | { | ||
| 401 | this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers) | ||
| 402 | { | ||
| 403 | SymbolId = productCode, | ||
| 404 | SymbolType = SymbolPathType.Product, | ||
| 405 | SymbolPaths = symbols, | ||
| 406 | }); | ||
| 407 | } | ||
| 408 | } | ||
| 409 | } | ||
| 410 | finally | ||
| 411 | { | ||
| 412 | this.compilingProduct = false; | ||
| 413 | } | ||
| 414 | } | ||
| 415 | |||
| 416 | private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion) | ||
| 417 | { | ||
| 418 | // Let's default to a modern version of MSI. Users can override, | ||
| 419 | // of course, subject to platform-specific limitations. | ||
| 420 | msiVersion = 500; | ||
| 421 | |||
| 422 | switch (this.CurrentPlatform) | ||
| 423 | { | ||
| 424 | case Platform.X86: | ||
| 425 | platform = "Intel"; | ||
| 426 | break; | ||
| 427 | case Platform.X64: | ||
| 428 | platform = "x64"; | ||
| 429 | break; | ||
| 430 | case Platform.ARM64: | ||
| 431 | platform = "Arm64"; | ||
| 432 | break; | ||
| 433 | default: | ||
| 434 | throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform, string language) | ||
| 439 | { | ||
| 440 | if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) | ||
| 441 | { | ||
| 442 | msiVersion = 200; | ||
| 443 | this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); | ||
| 444 | } | ||
| 445 | |||
| 446 | if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) | ||
| 447 | { | ||
| 448 | msiVersion = 500; | ||
| 449 | this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); | ||
| 450 | } | ||
| 451 | |||
| 452 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 453 | { | ||
| 454 | PropertyId = SummaryInformationType.Comments, | ||
| 455 | Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName) | ||
| 456 | }); | ||
| 457 | |||
| 458 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 459 | { | ||
| 460 | PropertyId = SummaryInformationType.Title, | ||
| 461 | Value = "Installation Database" | ||
| 462 | }); | ||
| 463 | |||
| 464 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 465 | { | ||
| 466 | PropertyId = SummaryInformationType.PlatformAndLanguage, | ||
| 467 | Value = $"{platform};{language}" | ||
| 468 | }); | ||
| 469 | |||
| 470 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 471 | { | ||
| 472 | PropertyId = SummaryInformationType.WindowsInstallerVersion, | ||
| 473 | Value = msiVersion.ToString(CultureInfo.InvariantCulture) | ||
| 474 | }); | ||
| 475 | |||
| 476 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 477 | { | ||
| 478 | PropertyId = SummaryInformationType.Security, | ||
| 479 | Value = "2" | ||
| 480 | }); | ||
| 481 | } | ||
| 482 | |||
| 483 | /// <summary> | ||
| 484 | /// Parses an odbc driver or translator element. | ||
| 485 | /// </summary> | ||
| 486 | /// <param name="node">Element to parse.</param> | ||
| 487 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 488 | /// <param name="fileId">Default identifer for driver/translator file.</param> | ||
| 489 | /// <param name="symbolDefinitionType">Symbol type we're processing for.</param> | ||
| 490 | private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, SymbolDefinitionType symbolDefinitionType) | ||
| 491 | { | ||
| 492 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 493 | Identifier id = null; | ||
| 494 | var driver = fileId; | ||
| 495 | string name = null; | ||
| 496 | var setup = fileId; | ||
| 497 | |||
| 498 | foreach (var attrib in node.Attributes()) | ||
| 499 | { | ||
| 500 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 501 | { | ||
| 502 | switch (attrib.Name.LocalName) | ||
| 503 | { | ||
| 504 | case "Id": | ||
| 505 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 506 | break; | ||
| 507 | case "File": | ||
| 508 | driver = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 509 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, driver); | ||
| 510 | break; | ||
| 511 | case "Name": | ||
| 512 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 513 | break; | ||
| 514 | case "SetupFile": | ||
| 515 | setup = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 516 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, setup); | ||
| 517 | break; | ||
| 518 | default: | ||
| 519 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 520 | break; | ||
| 521 | } | ||
| 522 | } | ||
| 523 | else | ||
| 524 | { | ||
| 525 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 526 | } | ||
| 527 | } | ||
| 528 | |||
| 529 | if (null == name) | ||
| 530 | { | ||
| 531 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 532 | } | ||
| 533 | |||
| 534 | if (null == id) | ||
| 535 | { | ||
| 536 | id = this.Core.CreateIdentifier("odb", name, fileId, setup); | ||
| 537 | } | ||
| 538 | |||
| 539 | // drivers have a few possible children | ||
| 540 | if (SymbolDefinitionType.ODBCDriver == symbolDefinitionType) | ||
| 541 | { | ||
| 542 | // process any data sources for the driver | ||
| 543 | foreach (var child in node.Elements()) | ||
| 544 | { | ||
| 545 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 546 | { | ||
| 547 | switch (child.Name.LocalName) | ||
| 548 | { | ||
| 549 | case "ODBCDataSource": | ||
| 550 | string ignoredKeyPath = null; | ||
| 551 | this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath); | ||
| 552 | break; | ||
| 553 | case "Property": | ||
| 554 | this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCAttribute); | ||
| 555 | break; | ||
| 556 | default: | ||
| 557 | this.Core.UnexpectedElement(node, child); | ||
| 558 | break; | ||
| 559 | } | ||
| 560 | } | ||
| 561 | else | ||
| 562 | { | ||
| 563 | this.Core.ParseExtensionElement(node, child); | ||
| 564 | } | ||
| 565 | } | ||
| 566 | } | ||
| 567 | else | ||
| 568 | { | ||
| 569 | this.Core.ParseForExtensionElements(node); | ||
| 570 | } | ||
| 571 | |||
| 572 | if (!this.Core.EncounteredError) | ||
| 573 | { | ||
| 574 | switch (symbolDefinitionType) | ||
| 575 | { | ||
| 576 | case SymbolDefinitionType.ODBCDriver: | ||
| 577 | this.Core.AddSymbol(new ODBCDriverSymbol(sourceLineNumbers, id) | ||
| 578 | { | ||
| 579 | ComponentRef = componentId, | ||
| 580 | Description = name, | ||
| 581 | FileRef = driver, | ||
| 582 | SetupFileRef = setup, | ||
| 583 | }); | ||
| 584 | break; | ||
| 585 | case SymbolDefinitionType.ODBCTranslator: | ||
| 586 | this.Core.AddSymbol(new ODBCTranslatorSymbol(sourceLineNumbers, id) | ||
| 587 | { | ||
| 588 | ComponentRef = componentId, | ||
| 589 | Description = name, | ||
| 590 | FileRef = driver, | ||
| 591 | SetupFileRef = setup, | ||
| 592 | }); | ||
| 593 | break; | ||
| 594 | default: | ||
| 595 | throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); | ||
| 596 | } | ||
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 | /// <summary> | ||
| 601 | /// Parses a Property element underneath an ODBC driver or translator. | ||
| 602 | /// </summary> | ||
| 603 | /// <param name="node">Element to parse.</param> | ||
| 604 | /// <param name="parentId">Identifier of parent driver or translator.</param> | ||
| 605 | /// <param name="symbolDefinitionType">Name of the table to create property in.</param> | ||
| 606 | private void ParseODBCProperty(XElement node, string parentId, SymbolDefinitionType symbolDefinitionType) | ||
| 607 | { | ||
| 608 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 609 | string id = null; | ||
| 610 | string propertyValue = null; | ||
| 611 | |||
| 612 | foreach (var attrib in node.Attributes()) | ||
| 613 | { | ||
| 614 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 615 | { | ||
| 616 | switch (attrib.Name.LocalName) | ||
| 617 | { | ||
| 618 | case "Id": | ||
| 619 | id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 620 | break; | ||
| 621 | case "Value": | ||
| 622 | propertyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 623 | break; | ||
| 624 | default: | ||
| 625 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 626 | break; | ||
| 627 | } | ||
| 628 | } | ||
| 629 | else | ||
| 630 | { | ||
| 631 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 632 | } | ||
| 633 | } | ||
| 634 | |||
| 635 | if (null == id) | ||
| 636 | { | ||
| 637 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 638 | } | ||
| 639 | |||
| 640 | this.Core.ParseForExtensionElements(node); | ||
| 641 | |||
| 642 | if (!this.Core.EncounteredError) | ||
| 643 | { | ||
| 644 | var identifier = new Identifier(AccessModifier.Section, parentId, id); | ||
| 645 | switch (symbolDefinitionType) | ||
| 646 | { | ||
| 647 | case SymbolDefinitionType.ODBCAttribute: | ||
| 648 | this.Core.AddSymbol(new ODBCAttributeSymbol(sourceLineNumbers, identifier) | ||
| 649 | { | ||
| 650 | DriverRef = parentId, | ||
| 651 | Attribute = id, | ||
| 652 | Value = propertyValue, | ||
| 653 | }); | ||
| 654 | break; | ||
| 655 | case SymbolDefinitionType.ODBCSourceAttribute: | ||
| 656 | this.Core.AddSymbol(new ODBCSourceAttributeSymbol(sourceLineNumbers, identifier) | ||
| 657 | { | ||
| 658 | DataSourceRef = parentId, | ||
| 659 | Attribute = id, | ||
| 660 | Value = propertyValue, | ||
| 661 | }); | ||
| 662 | break; | ||
| 663 | default: | ||
| 664 | throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); | ||
| 665 | } | ||
| 666 | } | ||
| 667 | } | ||
| 668 | |||
| 669 | /// <summary> | ||
| 670 | /// Parse an odbc data source element. | ||
| 671 | /// </summary> | ||
| 672 | /// <param name="node">Element to parse.</param> | ||
| 673 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 674 | /// <param name="driverName">Default name of driver.</param> | ||
| 675 | /// <param name="possibleKeyPath">Identifier of this element in case it is a keypath.</param> | ||
| 676 | /// <returns>Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise.</returns> | ||
| 677 | private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath) | ||
| 678 | { | ||
| 679 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 680 | Identifier id = null; | ||
| 681 | var keyPath = YesNoType.NotSet; | ||
| 682 | string name = null; | ||
| 683 | var registration = CompilerConstants.IntegerNotSet; | ||
| 684 | |||
| 685 | foreach (var attrib in node.Attributes()) | ||
| 686 | { | ||
| 687 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 688 | { | ||
| 689 | switch (attrib.Name.LocalName) | ||
| 690 | { | ||
| 691 | case "Id": | ||
| 692 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 693 | break; | ||
| 694 | case "DriverName": | ||
| 695 | driverName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 696 | break; | ||
| 697 | case "KeyPath": | ||
| 698 | keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 699 | break; | ||
| 700 | case "Name": | ||
| 701 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 702 | break; | ||
| 703 | case "Registration": | ||
| 704 | var registrationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 705 | switch (registrationValue) | ||
| 706 | { | ||
| 707 | case "machine": | ||
| 708 | registration = 0; | ||
| 709 | break; | ||
| 710 | case "user": | ||
| 711 | registration = 1; | ||
| 712 | break; | ||
| 713 | case "": | ||
| 714 | break; | ||
| 715 | default: | ||
| 716 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user")); | ||
| 717 | break; | ||
| 718 | } | ||
| 719 | break; | ||
| 720 | default: | ||
| 721 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 722 | break; | ||
| 723 | } | ||
| 724 | } | ||
| 725 | else | ||
| 726 | { | ||
| 727 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 728 | } | ||
| 729 | } | ||
| 730 | |||
| 731 | if (CompilerConstants.IntegerNotSet == registration) | ||
| 732 | { | ||
| 733 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration")); | ||
| 734 | registration = CompilerConstants.IllegalInteger; | ||
| 735 | } | ||
| 736 | |||
| 737 | if (null == id) | ||
| 738 | { | ||
| 739 | id = this.Core.CreateIdentifier("odc", name, driverName, registration.ToString()); | ||
| 740 | } | ||
| 741 | |||
| 742 | foreach (var child in node.Elements()) | ||
| 743 | { | ||
| 744 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 745 | { | ||
| 746 | switch (child.Name.LocalName) | ||
| 747 | { | ||
| 748 | case "Property": | ||
| 749 | this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCSourceAttribute); | ||
| 750 | break; | ||
| 751 | default: | ||
| 752 | this.Core.UnexpectedElement(node, child); | ||
| 753 | break; | ||
| 754 | } | ||
| 755 | } | ||
| 756 | else | ||
| 757 | { | ||
| 758 | this.Core.ParseExtensionElement(node, child); | ||
| 759 | } | ||
| 760 | } | ||
| 761 | |||
| 762 | if (!this.Core.EncounteredError) | ||
| 763 | { | ||
| 764 | this.Core.AddSymbol(new ODBCDataSourceSymbol(sourceLineNumbers, id) | ||
| 765 | { | ||
| 766 | ComponentRef = componentId, | ||
| 767 | Description = name, | ||
| 768 | DriverDescription = driverName, | ||
| 769 | Registration = registration | ||
| 770 | }); | ||
| 771 | } | ||
| 772 | |||
| 773 | possibleKeyPath = id.Id; | ||
| 774 | return keyPath; | ||
| 775 | } | ||
| 776 | |||
| 777 | /// <summary> | ||
| 778 | /// Parses a package element. | ||
| 779 | /// </summary> | ||
| 780 | /// <param name="node">Element to parse.</param> | ||
| 781 | /// <param name="isCodepageSet"></param> | ||
| 782 | /// <param name="isPackageNameSet"></param> | ||
| 783 | /// <param name="isKeywordsSet"></param> | ||
| 784 | /// <param name="isPackageAuthorSet"></param> | ||
| 785 | private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet) | ||
| 786 | { | ||
| 787 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 788 | string codepage = null; | ||
| 789 | string packageName = null; | ||
| 790 | string keywords = null; | ||
| 791 | string packageAuthor = null; | ||
| 792 | |||
| 793 | foreach (var attrib in node.Attributes()) | ||
| 794 | { | ||
| 795 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 796 | { | ||
| 797 | switch (attrib.Name.LocalName) | ||
| 798 | { | ||
| 799 | case "Codepage": | ||
| 800 | codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); | ||
| 801 | break; | ||
| 802 | case "Description": | ||
| 803 | packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 804 | break; | ||
| 805 | case "Keywords": | ||
| 806 | keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 807 | break; | ||
| 808 | case "Manufacturer": | ||
| 809 | packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 810 | if ("PUT-COMPANY-NAME-HERE" == packageAuthor) | ||
| 811 | { | ||
| 812 | this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); | ||
| 813 | } | ||
| 814 | break; | ||
| 815 | default: | ||
| 816 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 817 | break; | ||
| 818 | } | ||
| 819 | } | ||
| 820 | else | ||
| 821 | { | ||
| 822 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 823 | } | ||
| 824 | } | ||
| 825 | |||
| 826 | this.Core.ParseForExtensionElements(node); | ||
| 827 | |||
| 828 | if (!this.Core.EncounteredError) | ||
| 829 | { | ||
| 830 | if (null != codepage) | ||
| 831 | { | ||
| 832 | isCodepageSet = true; | ||
| 833 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 834 | { | ||
| 835 | PropertyId = SummaryInformationType.Codepage, | ||
| 836 | Value = codepage | ||
| 837 | }); | ||
| 838 | } | ||
| 839 | |||
| 840 | if (null != packageName) | ||
| 841 | { | ||
| 842 | isPackageNameSet = true; | ||
| 843 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 844 | { | ||
| 845 | PropertyId = SummaryInformationType.Subject, | ||
| 846 | Value = packageName | ||
| 847 | }); | ||
| 848 | } | ||
| 849 | |||
| 850 | if (null != packageAuthor) | ||
| 851 | { | ||
| 852 | isPackageAuthorSet = true; | ||
| 853 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 854 | { | ||
| 855 | PropertyId = SummaryInformationType.Author, | ||
| 856 | Value = packageAuthor | ||
| 857 | }); | ||
| 858 | } | ||
| 859 | |||
| 860 | if (null != keywords) | ||
| 861 | { | ||
| 862 | isKeywordsSet = true; | ||
| 863 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 864 | { | ||
| 865 | PropertyId = SummaryInformationType.Keywords, | ||
| 866 | Value = keywords | ||
| 867 | }); | ||
| 868 | } | ||
| 869 | } | ||
| 870 | } | ||
| 871 | |||
| 872 | /// <summary> | ||
| 873 | /// Parses a patch information element. | ||
| 874 | /// </summary> | ||
| 875 | /// <param name="node">Element to parse.</param> | ||
| 876 | private void ParsePatchInformationElement(XElement node) | ||
| 877 | { | ||
| 878 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 879 | var codepage = "1252"; | ||
| 880 | string comments = null; | ||
| 881 | var keywords = "Installer,Patching,PCP,Database"; | ||
| 882 | var msiVersion = 1; // Should always be 1 for patches | ||
| 883 | string packageAuthor = null; | ||
| 884 | var packageName = this.activeName; | ||
| 885 | var security = YesNoDefaultType.Default; | ||
| 886 | |||
| 887 | foreach (var attrib in node.Attributes()) | ||
| 888 | { | ||
| 889 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 890 | { | ||
| 891 | switch (attrib.Name.LocalName) | ||
| 892 | { | ||
| 893 | case "AdminImage": | ||
| 894 | this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
| 895 | break; | ||
| 896 | case "Comments": | ||
| 897 | comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 898 | break; | ||
| 899 | case "Compressed": | ||
| 900 | this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
| 901 | break; | ||
| 902 | case "Description": | ||
| 903 | packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 904 | break; | ||
| 905 | case "Keywords": | ||
| 906 | keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 907 | break; | ||
| 908 | case "Languages": | ||
| 909 | this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
| 910 | break; | ||
| 911 | case "Manufacturer": | ||
| 912 | packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 913 | break; | ||
| 914 | case "Platforms": | ||
| 915 | this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
| 916 | break; | ||
| 917 | case "ReadOnly": | ||
| 918 | security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); | ||
| 919 | break; | ||
| 920 | case "ShortNames": | ||
| 921 | this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
| 922 | break; | ||
| 923 | case "SummaryCodepage": | ||
| 924 | codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); | ||
| 925 | break; | ||
| 926 | default: | ||
| 927 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 928 | break; | ||
| 929 | } | ||
| 930 | } | ||
| 931 | else | ||
| 932 | { | ||
| 933 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 934 | } | ||
| 935 | } | ||
| 936 | |||
| 937 | this.Core.ParseForExtensionElements(node); | ||
| 938 | |||
| 939 | if (!this.Core.EncounteredError) | ||
| 940 | { | ||
| 941 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 942 | { | ||
| 943 | PropertyId = SummaryInformationType.Codepage, | ||
| 944 | Value = codepage | ||
| 945 | }); | ||
| 946 | |||
| 947 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 948 | { | ||
| 949 | PropertyId = SummaryInformationType.Title, | ||
| 950 | Value = "Patch" | ||
| 951 | }); | ||
| 952 | |||
| 953 | if (null != packageName) | ||
| 954 | { | ||
| 955 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 956 | { | ||
| 957 | PropertyId = SummaryInformationType.Subject, | ||
| 958 | Value = packageName | ||
| 959 | }); | ||
| 960 | } | ||
| 961 | |||
| 962 | if (null != packageAuthor) | ||
| 963 | { | ||
| 964 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 965 | { | ||
| 966 | PropertyId = SummaryInformationType.Author, | ||
| 967 | Value = packageAuthor | ||
| 968 | }); | ||
| 969 | } | ||
| 970 | |||
| 971 | if (null != keywords) | ||
| 972 | { | ||
| 973 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 974 | { | ||
| 975 | PropertyId = SummaryInformationType.Keywords, | ||
| 976 | Value = keywords | ||
| 977 | }); | ||
| 978 | } | ||
| 979 | |||
| 980 | if (null != comments) | ||
| 981 | { | ||
| 982 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 983 | { | ||
| 984 | PropertyId = SummaryInformationType.Comments, | ||
| 985 | Value = comments | ||
| 986 | }); | ||
| 987 | } | ||
| 988 | |||
| 989 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 990 | { | ||
| 991 | PropertyId = SummaryInformationType.WindowsInstallerVersion, | ||
| 992 | Value = msiVersion.ToString(CultureInfo.InvariantCulture) | ||
| 993 | }); | ||
| 994 | |||
| 995 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 996 | { | ||
| 997 | PropertyId = SummaryInformationType.WordCount, | ||
| 998 | Value = "0" | ||
| 999 | }); | ||
| 1000 | |||
| 1001 | this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) | ||
| 1002 | { | ||
| 1003 | PropertyId = SummaryInformationType.Security, | ||
| 1004 | Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2" | ||
| 1005 | }); | ||
| 1006 | } | ||
| 1007 | } | ||
| 1008 | |||
| 1009 | /// <summary> | ||
| 1010 | /// Parses a permission element. | ||
| 1011 | /// </summary> | ||
| 1012 | /// <param name="node">Element to parse.</param> | ||
| 1013 | /// <param name="objectId">Identifier of object to be secured.</param> | ||
| 1014 | /// <param name="tableName">Name of table that contains objectId.</param> | ||
| 1015 | private void ParsePermissionElement(XElement node, string objectId, string tableName) | ||
| 1016 | { | ||
| 1017 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1018 | var bits = new BitArray(32); | ||
| 1019 | string domain = null; | ||
| 1020 | string[] specialPermissions = null; | ||
| 1021 | string user = null; | ||
| 1022 | |||
| 1023 | switch (tableName) | ||
| 1024 | { | ||
| 1025 | case "CreateFolder": | ||
| 1026 | specialPermissions = LockPermissionConstants.FolderPermissions; | ||
| 1027 | break; | ||
| 1028 | case "File": | ||
| 1029 | specialPermissions = LockPermissionConstants.FilePermissions; | ||
| 1030 | break; | ||
| 1031 | case "Registry": | ||
| 1032 | specialPermissions = LockPermissionConstants.RegistryPermissions; | ||
| 1033 | break; | ||
| 1034 | default: | ||
| 1035 | this.Core.UnexpectedElement(node.Parent, node); | ||
| 1036 | return; // stop processing this element since no valid permissions are available | ||
| 1037 | } | ||
| 1038 | |||
| 1039 | foreach (var attrib in node.Attributes()) | ||
| 1040 | { | ||
| 1041 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1042 | { | ||
| 1043 | switch (attrib.Name.LocalName) | ||
| 1044 | { | ||
| 1045 | case "Domain": | ||
| 1046 | domain = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1047 | break; | ||
| 1048 | case "User": | ||
| 1049 | user = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1050 | break; | ||
| 1051 | case "FileAllRights": | ||
| 1052 | // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127) | ||
| 1053 | bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[16] = bits[17] = bits[18] = bits[19] = bits[20] = true; | ||
| 1054 | break; | ||
| 1055 | case "SpecificRightsAll": | ||
| 1056 | // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111) | ||
| 1057 | bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[9] = bits[10] = bits[11] = bits[12] = bits[13] = bits[14] = bits[15] = true; | ||
| 1058 | break; | ||
| 1059 | default: | ||
| 1060 | var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1061 | if (!this.Core.TrySetBitFromName(LockPermissionConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16)) | ||
| 1062 | { | ||
| 1063 | if (!this.Core.TrySetBitFromName(LockPermissionConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28)) | ||
| 1064 | { | ||
| 1065 | if (!this.Core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0)) | ||
| 1066 | { | ||
| 1067 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1068 | break; | ||
| 1069 | } | ||
| 1070 | } | ||
| 1071 | } | ||
| 1072 | break; | ||
| 1073 | } | ||
| 1074 | } | ||
| 1075 | else | ||
| 1076 | { | ||
| 1077 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1078 | } | ||
| 1079 | } | ||
| 1080 | |||
| 1081 | if (null == user) | ||
| 1082 | { | ||
| 1083 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User")); | ||
| 1084 | } | ||
| 1085 | |||
| 1086 | var permission = this.Core.CreateIntegerFromBitArray(bits); | ||
| 1087 | |||
| 1088 | if (Int32.MinValue == permission) // just GENERIC_READ, which is MSI_NULL | ||
| 1089 | { | ||
| 1090 | this.Core.Write(ErrorMessages.GenericReadNotAllowed(sourceLineNumbers)); | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | this.Core.ParseForExtensionElements(node); | ||
| 1094 | |||
| 1095 | if (!this.Core.EncounteredError) | ||
| 1096 | { | ||
| 1097 | this.Core.AddSymbol(new LockPermissionsSymbol(sourceLineNumbers) | ||
| 1098 | { | ||
| 1099 | LockObject = objectId, | ||
| 1100 | Table = tableName, | ||
| 1101 | Domain = domain, | ||
| 1102 | User = user, | ||
| 1103 | Permission = permission | ||
| 1104 | }); | ||
| 1105 | } | ||
| 1106 | } | ||
| 1107 | |||
| 1108 | /// <summary> | ||
| 1109 | /// Parses an extended permission element. | ||
| 1110 | /// </summary> | ||
| 1111 | /// <param name="node">Element to parse.</param> | ||
| 1112 | /// <param name="objectId">Identifier of object to be secured.</param> | ||
| 1113 | /// <param name="tableName">Name of table that contains objectId.</param> | ||
| 1114 | private void ParsePermissionExElement(XElement node, string objectId, string tableName) | ||
| 1115 | { | ||
| 1116 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1117 | string condition = null; | ||
| 1118 | Identifier id = null; | ||
| 1119 | string sddl = null; | ||
| 1120 | |||
| 1121 | switch (tableName) | ||
| 1122 | { | ||
| 1123 | case "CreateFolder": | ||
| 1124 | case "File": | ||
| 1125 | case "Registry": | ||
| 1126 | case "ServiceInstall": | ||
| 1127 | break; | ||
| 1128 | default: | ||
| 1129 | this.Core.UnexpectedElement(node.Parent, node); | ||
| 1130 | return; // stop processing this element since nothing will be valid. | ||
| 1131 | } | ||
| 1132 | |||
| 1133 | foreach (var attrib in node.Attributes()) | ||
| 1134 | { | ||
| 1135 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1136 | { | ||
| 1137 | switch (attrib.Name.LocalName) | ||
| 1138 | { | ||
| 1139 | case "Id": | ||
| 1140 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1141 | break; | ||
| 1142 | case "Condition": | ||
| 1143 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1144 | break; | ||
| 1145 | case "Sddl": | ||
| 1146 | sddl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1147 | break; | ||
| 1148 | default: | ||
| 1149 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1150 | break; | ||
| 1151 | } | ||
| 1152 | } | ||
| 1153 | else | ||
| 1154 | { | ||
| 1155 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1156 | } | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | if (null == sddl) | ||
| 1160 | { | ||
| 1161 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); | ||
| 1162 | } | ||
| 1163 | |||
| 1164 | if (null == id) | ||
| 1165 | { | ||
| 1166 | id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl); | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | this.Core.ParseForExtensionElements(node); | ||
| 1170 | |||
| 1171 | if (!this.Core.EncounteredError) | ||
| 1172 | { | ||
| 1173 | this.Core.AddSymbol(new MsiLockPermissionsExSymbol(sourceLineNumbers, id) | ||
| 1174 | { | ||
| 1175 | LockObject = objectId, | ||
| 1176 | Table = tableName, | ||
| 1177 | SDDLText = sddl, | ||
| 1178 | Condition = condition | ||
| 1179 | }); | ||
| 1180 | } | ||
| 1181 | } | ||
| 1182 | |||
| 1183 | /// <summary> | ||
| 1184 | /// Parses a progid element | ||
| 1185 | /// </summary> | ||
| 1186 | /// <param name="node">Element to parse.</param> | ||
| 1187 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 1188 | /// <param name="advertise">Flag if progid is advertised.</param> | ||
| 1189 | /// <param name="classId">CLSID related to ProgId.</param> | ||
| 1190 | /// <param name="description">Default description of ProgId</param> | ||
| 1191 | /// <param name="parent">Optional parent ProgId</param> | ||
| 1192 | /// <param name="foundExtension">Set to true if an extension is found; used for error-checking.</param> | ||
| 1193 | /// <param name="firstProgIdForClass">Whether or not this ProgId is the first one found in the parent class.</param> | ||
| 1194 | /// <returns>This element's Id.</returns> | ||
| 1195 | private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass) | ||
| 1196 | { | ||
| 1197 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1198 | string icon = null; | ||
| 1199 | var iconIndex = CompilerConstants.IntegerNotSet; | ||
| 1200 | string noOpen = null; | ||
| 1201 | string progId = null; | ||
| 1202 | var progIdAdvertise = YesNoType.NotSet; | ||
| 1203 | |||
| 1204 | foreach (var attrib in node.Attributes()) | ||
| 1205 | { | ||
| 1206 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1207 | { | ||
| 1208 | switch (attrib.Name.LocalName) | ||
| 1209 | { | ||
| 1210 | case "Id": | ||
| 1211 | progId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1212 | break; | ||
| 1213 | case "Advertise": | ||
| 1214 | progIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1215 | break; | ||
| 1216 | case "Description": | ||
| 1217 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 1218 | break; | ||
| 1219 | case "Icon": | ||
| 1220 | icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 1221 | break; | ||
| 1222 | case "IconIndex": | ||
| 1223 | iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); | ||
| 1224 | break; | ||
| 1225 | case "NoOpen": | ||
| 1226 | noOpen = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 1227 | break; | ||
| 1228 | default: | ||
| 1229 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1230 | break; | ||
| 1231 | } | ||
| 1232 | } | ||
| 1233 | else | ||
| 1234 | { | ||
| 1235 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1236 | } | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise)) | ||
| 1240 | { | ||
| 1241 | this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString())); | ||
| 1242 | } | ||
| 1243 | else if (YesNoType.NotSet != progIdAdvertise) | ||
| 1244 | { | ||
| 1245 | advertise = progIdAdvertise; | ||
| 1246 | } | ||
| 1247 | |||
| 1248 | if (YesNoType.NotSet == advertise) | ||
| 1249 | { | ||
| 1250 | advertise = YesNoType.No; | ||
| 1251 | } | ||
| 1252 | |||
| 1253 | if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex)) | ||
| 1254 | { | ||
| 1255 | this.Core.Write(ErrorMessages.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers)); | ||
| 1256 | } | ||
| 1257 | |||
| 1258 | var firstProgIdForNestedClass = YesNoType.Yes; | ||
| 1259 | foreach (var child in node.Elements()) | ||
| 1260 | { | ||
| 1261 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1262 | { | ||
| 1263 | switch (child.Name.LocalName) | ||
| 1264 | { | ||
| 1265 | case "Extension": | ||
| 1266 | this.ParseExtensionElement(child, componentId, advertise, progId); | ||
| 1267 | foundExtension = true; | ||
| 1268 | break; | ||
| 1269 | case "ProgId": | ||
| 1270 | // Only allow one nested ProgId. If we have a child, we should not have a parent. | ||
| 1271 | if (null == parent) | ||
| 1272 | { | ||
| 1273 | if (YesNoType.Yes == advertise) | ||
| 1274 | { | ||
| 1275 | this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass); | ||
| 1276 | } | ||
| 1277 | else if (YesNoType.No == advertise) | ||
| 1278 | { | ||
| 1279 | this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass); | ||
| 1280 | } | ||
| 1281 | |||
| 1282 | firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first. | ||
| 1283 | } | ||
| 1284 | else | ||
| 1285 | { | ||
| 1286 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 1287 | this.Core.Write(ErrorMessages.ProgIdNestedTooDeep(childSourceLineNumbers)); | ||
| 1288 | } | ||
| 1289 | break; | ||
| 1290 | default: | ||
| 1291 | this.Core.UnexpectedElement(node, child); | ||
| 1292 | break; | ||
| 1293 | } | ||
| 1294 | } | ||
| 1295 | else | ||
| 1296 | { | ||
| 1297 | this.Core.ParseExtensionElement(node, child); | ||
| 1298 | } | ||
| 1299 | } | ||
| 1300 | |||
| 1301 | if (YesNoType.Yes == advertise) | ||
| 1302 | { | ||
| 1303 | if (!this.Core.EncounteredError) | ||
| 1304 | { | ||
| 1305 | var symbol = this.Core.AddSymbol(new ProgIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, progId)) | ||
| 1306 | { | ||
| 1307 | ProgId = progId, | ||
| 1308 | ParentProgIdRef = parent, | ||
| 1309 | ClassRef = classId, | ||
| 1310 | Description = description, | ||
| 1311 | }); | ||
| 1312 | |||
| 1313 | if (null != icon) | ||
| 1314 | { | ||
| 1315 | symbol.IconRef = icon; | ||
| 1316 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); | ||
| 1317 | } | ||
| 1318 | |||
| 1319 | if (CompilerConstants.IntegerNotSet != iconIndex) | ||
| 1320 | { | ||
| 1321 | symbol.IconIndex = iconIndex; | ||
| 1322 | } | ||
| 1323 | |||
| 1324 | this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Class); | ||
| 1325 | } | ||
| 1326 | } | ||
| 1327 | else if (YesNoType.No == advertise) | ||
| 1328 | { | ||
| 1329 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, String.Empty, description, componentId); | ||
| 1330 | if (null != classId) | ||
| 1331 | { | ||
| 1332 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId); | ||
| 1333 | if (null != parent) // if this is a version independent ProgId | ||
| 1334 | { | ||
| 1335 | if (YesNoType.Yes == firstProgIdForClass) | ||
| 1336 | { | ||
| 1337 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId); | ||
| 1338 | } | ||
| 1339 | |||
| 1340 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId); | ||
| 1341 | } | ||
| 1342 | else | ||
| 1343 | { | ||
| 1344 | if (YesNoType.Yes == firstProgIdForClass) | ||
| 1345 | { | ||
| 1346 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId); | ||
| 1347 | } | ||
| 1348 | } | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | if (null != icon) // ProgId's Default Icon | ||
| 1352 | { | ||
| 1353 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); | ||
| 1354 | |||
| 1355 | icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); | ||
| 1356 | |||
| 1357 | if (CompilerConstants.IntegerNotSet != iconIndex) | ||
| 1358 | { | ||
| 1359 | icon = String.Concat(icon, ",", iconIndex); | ||
| 1360 | } | ||
| 1361 | |||
| 1362 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId); | ||
| 1363 | } | ||
| 1364 | } | ||
| 1365 | |||
| 1366 | if (null != noOpen) | ||
| 1367 | { | ||
| 1368 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name | ||
| 1369 | } | ||
| 1370 | |||
| 1371 | // raise an error for an orphaned ProgId | ||
| 1372 | if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId) | ||
| 1373 | { | ||
| 1374 | this.Core.Write(WarningMessages.OrphanedProgId(sourceLineNumbers, progId)); | ||
| 1375 | } | ||
| 1376 | |||
| 1377 | return progId; | ||
| 1378 | } | ||
| 1379 | |||
| 1380 | /// <summary> | ||
| 1381 | /// Parses a property element. | ||
| 1382 | /// </summary> | ||
| 1383 | /// <param name="node">Element to parse.</param> | ||
| 1384 | private void ParsePropertyElement(XElement node) | ||
| 1385 | { | ||
| 1386 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1387 | Identifier id = null; | ||
| 1388 | var admin = false; | ||
| 1389 | var complianceCheck = false; | ||
| 1390 | var hidden = false; | ||
| 1391 | var secure = false; | ||
| 1392 | var suppressModularization = YesNoType.NotSet; | ||
| 1393 | string value = null; | ||
| 1394 | |||
| 1395 | foreach (var attrib in node.Attributes()) | ||
| 1396 | { | ||
| 1397 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1398 | { | ||
| 1399 | switch (attrib.Name.LocalName) | ||
| 1400 | { | ||
| 1401 | case "Id": | ||
| 1402 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1403 | break; | ||
| 1404 | case "Admin": | ||
| 1405 | admin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1406 | break; | ||
| 1407 | case "ComplianceCheck": | ||
| 1408 | complianceCheck = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1409 | break; | ||
| 1410 | case "Hidden": | ||
| 1411 | hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1412 | break; | ||
| 1413 | case "Secure": | ||
| 1414 | secure = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1415 | break; | ||
| 1416 | case "SuppressModularization": | ||
| 1417 | suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1418 | break; | ||
| 1419 | case "Value": | ||
| 1420 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1421 | break; | ||
| 1422 | default: | ||
| 1423 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1424 | break; | ||
| 1425 | } | ||
| 1426 | } | ||
| 1427 | else | ||
| 1428 | { | ||
| 1429 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1430 | } | ||
| 1431 | } | ||
| 1432 | |||
| 1433 | if (null == id) | ||
| 1434 | { | ||
| 1435 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 1436 | id = Identifier.Invalid; | ||
| 1437 | } | ||
| 1438 | else if ("ProductID" == id.Id) | ||
| 1439 | { | ||
| 1440 | this.Core.Write(WarningMessages.ProductIdAuthored(sourceLineNumbers)); | ||
| 1441 | } | ||
| 1442 | else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id) | ||
| 1443 | { | ||
| 1444 | this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); | ||
| 1445 | } | ||
| 1446 | |||
| 1447 | if ("ErrorDialog" == id.Id) | ||
| 1448 | { | ||
| 1449 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value); | ||
| 1450 | } | ||
| 1451 | |||
| 1452 | foreach (var child in node.Elements()) | ||
| 1453 | { | ||
| 1454 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1455 | { | ||
| 1456 | { | ||
| 1457 | switch (child.Name.LocalName) | ||
| 1458 | { | ||
| 1459 | case "ProductSearch": | ||
| 1460 | this.ParseProductSearchElement(child, id.Id); | ||
| 1461 | secure = true; | ||
| 1462 | break; | ||
| 1463 | default: | ||
| 1464 | // let ParseSearchSignatures handle standard AppSearch children and unknown elements | ||
| 1465 | break; | ||
| 1466 | } | ||
| 1467 | } | ||
| 1468 | } | ||
| 1469 | } | ||
| 1470 | |||
| 1471 | this.Core.InnerTextDisallowed(node); | ||
| 1472 | |||
| 1473 | // see if this property is used for appSearch | ||
| 1474 | var signatures = this.ParseSearchSignatures(node); | ||
| 1475 | |||
| 1476 | // If we're doing CCP then there must be a signature. | ||
| 1477 | if (complianceCheck && 0 == signatures.Count) | ||
| 1478 | { | ||
| 1479 | this.Core.Write(ErrorMessages.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes")); | ||
| 1480 | } | ||
| 1481 | |||
| 1482 | foreach (var sig in signatures) | ||
| 1483 | { | ||
| 1484 | if (complianceCheck && !this.Core.EncounteredError) | ||
| 1485 | { | ||
| 1486 | this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, sig))); | ||
| 1487 | } | ||
| 1488 | |||
| 1489 | this.AddAppSearch(sourceLineNumbers, id, sig); | ||
| 1490 | } | ||
| 1491 | |||
| 1492 | // If we're doing AppSearch get that setup. | ||
| 1493 | if (0 < signatures.Count) | ||
| 1494 | { | ||
| 1495 | this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); | ||
| 1496 | } | ||
| 1497 | else // just a normal old property. | ||
| 1498 | { | ||
| 1499 | // If the property value is empty and none of the flags are set, print out a warning that we're ignoring | ||
| 1500 | // the element. | ||
| 1501 | if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden) | ||
| 1502 | { | ||
| 1503 | this.Core.Write(WarningMessages.PropertyUseless(sourceLineNumbers, id.Id)); | ||
| 1504 | } | ||
| 1505 | else // there is a value and/or a flag set, do that. | ||
| 1506 | { | ||
| 1507 | this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); | ||
| 1508 | } | ||
| 1509 | } | ||
| 1510 | |||
| 1511 | if (!this.Core.EncounteredError && YesNoType.Yes == suppressModularization) | ||
| 1512 | { | ||
| 1513 | this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers)); | ||
| 1514 | |||
| 1515 | this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) | ||
| 1516 | { | ||
| 1517 | SuppressIdentifier = id.Id | ||
| 1518 | }); | ||
| 1519 | } | ||
| 1520 | } | ||
| 1521 | |||
| 1522 | /// <summary> | ||
| 1523 | /// Parses a RegistryKey element. | ||
| 1524 | /// </summary> | ||
| 1525 | /// <param name="node">Element to parse.</param> | ||
| 1526 | /// <param name="componentId">Identifier for parent component.</param> | ||
| 1527 | /// <param name="root">Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet.</param> | ||
| 1528 | /// <param name="parentKey">Parent key for this Registry element when nested.</param> | ||
| 1529 | /// <param name="win64Component">true if the component is 64-bit.</param> | ||
| 1530 | /// <param name="possibleKeyPath">Identifier of this registry key since it could be the component's keypath.</param> | ||
| 1531 | /// <returns>Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise.</returns> | ||
| 1532 | private YesNoType ParseRegistryKeyElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) | ||
| 1533 | { | ||
| 1534 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1535 | Identifier id = null; | ||
| 1536 | var key = parentKey; // default to parent key path | ||
| 1537 | var forceCreateOnInstall = false; | ||
| 1538 | var forceDeleteOnUninstall = false; | ||
| 1539 | var keyPath = YesNoType.NotSet; | ||
| 1540 | |||
| 1541 | possibleKeyPath = null; | ||
| 1542 | |||
| 1543 | foreach (var attrib in node.Attributes()) | ||
| 1544 | { | ||
| 1545 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1546 | { | ||
| 1547 | switch (attrib.Name.LocalName) | ||
| 1548 | { | ||
| 1549 | case "Id": | ||
| 1550 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1551 | break; | ||
| 1552 | case "ForceCreateOnInstall": | ||
| 1553 | forceCreateOnInstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1554 | break; | ||
| 1555 | case "ForceDeleteOnUninstall": | ||
| 1556 | forceDeleteOnUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1557 | break; | ||
| 1558 | case "Key": | ||
| 1559 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1560 | if (null != parentKey) | ||
| 1561 | { | ||
| 1562 | key = Path.Combine(parentKey, key); | ||
| 1563 | } | ||
| 1564 | key = key?.TrimEnd('\\'); | ||
| 1565 | break; | ||
| 1566 | case "Root": | ||
| 1567 | if (root.HasValue) | ||
| 1568 | { | ||
| 1569 | this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); | ||
| 1570 | } | ||
| 1571 | |||
| 1572 | root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); | ||
| 1573 | break; | ||
| 1574 | default: | ||
| 1575 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1576 | break; | ||
| 1577 | } | ||
| 1578 | } | ||
| 1579 | else | ||
| 1580 | { | ||
| 1581 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1582 | } | ||
| 1583 | } | ||
| 1584 | |||
| 1585 | var name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null); | ||
| 1586 | |||
| 1587 | if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present | ||
| 1588 | { | ||
| 1589 | // generate the identifier if it wasn't provided | ||
| 1590 | if (null == id) | ||
| 1591 | { | ||
| 1592 | id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); | ||
| 1593 | } | ||
| 1594 | } | ||
| 1595 | else // does not generate a Registry row, so no Id should be present | ||
| 1596 | { | ||
| 1597 | if (null != id) | ||
| 1598 | { | ||
| 1599 | this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true)); | ||
| 1600 | } | ||
| 1601 | } | ||
| 1602 | |||
| 1603 | if (!root.HasValue) | ||
| 1604 | { | ||
| 1605 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); | ||
| 1606 | } | ||
| 1607 | |||
| 1608 | if (null == key) | ||
| 1609 | { | ||
| 1610 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 1611 | key = String.Empty; // set the key to something to prevent null reference exceptions | ||
| 1612 | } | ||
| 1613 | |||
| 1614 | foreach (var child in node.Elements()) | ||
| 1615 | { | ||
| 1616 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1617 | { | ||
| 1618 | string possibleChildKeyPath = null; | ||
| 1619 | |||
| 1620 | switch (child.Name.LocalName) | ||
| 1621 | { | ||
| 1622 | case "RegistryKey": | ||
| 1623 | if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) | ||
| 1624 | { | ||
| 1625 | if (YesNoType.Yes == keyPath) | ||
| 1626 | { | ||
| 1627 | this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); | ||
| 1628 | } | ||
| 1629 | |||
| 1630 | possibleKeyPath = possibleChildKeyPath; // the child is the key path | ||
| 1631 | keyPath = YesNoType.Yes; | ||
| 1632 | } | ||
| 1633 | else if (null == possibleKeyPath && null != possibleChildKeyPath) | ||
| 1634 | { | ||
| 1635 | possibleKeyPath = possibleChildKeyPath; | ||
| 1636 | } | ||
| 1637 | break; | ||
| 1638 | case "RegistryValue": | ||
| 1639 | if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) | ||
| 1640 | { | ||
| 1641 | if (YesNoType.Yes == keyPath) | ||
| 1642 | { | ||
| 1643 | this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); | ||
| 1644 | } | ||
| 1645 | |||
| 1646 | possibleKeyPath = possibleChildKeyPath; // the child is the key path | ||
| 1647 | keyPath = YesNoType.Yes; | ||
| 1648 | } | ||
| 1649 | else if (null == possibleKeyPath && null != possibleChildKeyPath) | ||
| 1650 | { | ||
| 1651 | possibleKeyPath = possibleChildKeyPath; | ||
| 1652 | } | ||
| 1653 | break; | ||
| 1654 | case "Permission": | ||
| 1655 | if (!forceCreateOnInstall) | ||
| 1656 | { | ||
| 1657 | this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); | ||
| 1658 | } | ||
| 1659 | this.ParsePermissionElement(child, id.Id, "Registry"); | ||
| 1660 | break; | ||
| 1661 | case "PermissionEx": | ||
| 1662 | if (!forceCreateOnInstall) | ||
| 1663 | { | ||
| 1664 | this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); | ||
| 1665 | } | ||
| 1666 | this.ParsePermissionExElement(child, id.Id, "Registry"); | ||
| 1667 | break; | ||
| 1668 | default: | ||
| 1669 | this.Core.UnexpectedElement(node, child); | ||
| 1670 | break; | ||
| 1671 | } | ||
| 1672 | } | ||
| 1673 | else | ||
| 1674 | { | ||
| 1675 | var context = new Dictionary<string, string>() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; | ||
| 1676 | this.Core.ParseExtensionElement(node, child, context); | ||
| 1677 | } | ||
| 1678 | } | ||
| 1679 | |||
| 1680 | if (!this.Core.EncounteredError && null != name) | ||
| 1681 | { | ||
| 1682 | this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) | ||
| 1683 | { | ||
| 1684 | Root = root.Value, | ||
| 1685 | Key = key, | ||
| 1686 | Name = name, | ||
| 1687 | ComponentRef = componentId, | ||
| 1688 | }); | ||
| 1689 | } | ||
| 1690 | |||
| 1691 | return keyPath; | ||
| 1692 | } | ||
| 1693 | |||
| 1694 | /// <summary> | ||
| 1695 | /// Parses a RegistryValue element. | ||
| 1696 | /// </summary> | ||
| 1697 | /// <param name="node">Element to parse.</param> | ||
| 1698 | /// <param name="componentId">Identifier for parent component.</param> | ||
| 1699 | /// <param name="root">Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet.</param> | ||
| 1700 | /// <param name="parentKey">Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet.</param> | ||
| 1701 | /// <param name="win64Component">true if the component is 64-bit.</param> | ||
| 1702 | /// <param name="possibleKeyPath">Identifier of this registry key since it could be the component's keypath.</param> | ||
| 1703 | /// <returns>Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise.</returns> | ||
| 1704 | private YesNoType ParseRegistryValueElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) | ||
| 1705 | { | ||
| 1706 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1707 | Identifier id = null; | ||
| 1708 | var key = parentKey; // default to parent key path | ||
| 1709 | string name = null; | ||
| 1710 | string value = null; | ||
| 1711 | string action = null; | ||
| 1712 | var valueType = RegistryValueType.String; | ||
| 1713 | var actionType = RegistryValueActionType.Write; | ||
| 1714 | var keyPath = YesNoType.NotSet; | ||
| 1715 | |||
| 1716 | possibleKeyPath = null; | ||
| 1717 | |||
| 1718 | foreach (var attrib in node.Attributes()) | ||
| 1719 | { | ||
| 1720 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1721 | { | ||
| 1722 | switch (attrib.Name.LocalName) | ||
| 1723 | { | ||
| 1724 | case "Id": | ||
| 1725 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1726 | break; | ||
| 1727 | case "Action": | ||
| 1728 | var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1729 | switch (actionValue) | ||
| 1730 | { | ||
| 1731 | case "append": | ||
| 1732 | actionType = RegistryValueActionType.Append; | ||
| 1733 | break; | ||
| 1734 | case "prepend": | ||
| 1735 | actionType = RegistryValueActionType.Prepend; | ||
| 1736 | break; | ||
| 1737 | case "write": | ||
| 1738 | actionType = RegistryValueActionType.Write; | ||
| 1739 | break; | ||
| 1740 | case "": | ||
| 1741 | break; | ||
| 1742 | default: | ||
| 1743 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "append", "prepend", "write")); | ||
| 1744 | break; | ||
| 1745 | } | ||
| 1746 | break; | ||
| 1747 | case "Key": | ||
| 1748 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1749 | if (null != parentKey) | ||
| 1750 | { | ||
| 1751 | if (parentKey.EndsWith("\\", StringComparison.Ordinal)) | ||
| 1752 | { | ||
| 1753 | key = String.Concat(parentKey, key); | ||
| 1754 | } | ||
| 1755 | else | ||
| 1756 | { | ||
| 1757 | key = String.Concat(parentKey, "\\", key); | ||
| 1758 | } | ||
| 1759 | } | ||
| 1760 | break; | ||
| 1761 | case "KeyPath": | ||
| 1762 | keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1763 | break; | ||
| 1764 | case "Name": | ||
| 1765 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1766 | break; | ||
| 1767 | case "Root": | ||
| 1768 | if (root.HasValue) | ||
| 1769 | { | ||
| 1770 | this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); | ||
| 1771 | } | ||
| 1772 | |||
| 1773 | root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); | ||
| 1774 | break; | ||
| 1775 | case "Type": | ||
| 1776 | var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1777 | switch (typeValue) | ||
| 1778 | { | ||
| 1779 | case "binary": | ||
| 1780 | valueType = RegistryValueType.Binary; | ||
| 1781 | break; | ||
| 1782 | case "expandable": | ||
| 1783 | valueType = RegistryValueType.Expandable; | ||
| 1784 | break; | ||
| 1785 | case "integer": | ||
| 1786 | valueType = RegistryValueType.Integer; | ||
| 1787 | break; | ||
| 1788 | case "multiString": | ||
| 1789 | valueType = RegistryValueType.MultiString; | ||
| 1790 | break; | ||
| 1791 | case "string": | ||
| 1792 | valueType = RegistryValueType.String; | ||
| 1793 | break; | ||
| 1794 | case "": | ||
| 1795 | break; | ||
| 1796 | default: | ||
| 1797 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "binary", "expandable", "integer", "multiString", "string")); | ||
| 1798 | break; | ||
| 1799 | } | ||
| 1800 | break; | ||
| 1801 | case "Value": | ||
| 1802 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 1803 | break; | ||
| 1804 | default: | ||
| 1805 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1806 | break; | ||
| 1807 | } | ||
| 1808 | } | ||
| 1809 | else | ||
| 1810 | { | ||
| 1811 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1812 | } | ||
| 1813 | } | ||
| 1814 | |||
| 1815 | // generate the identifier if it wasn't provided | ||
| 1816 | if (null == id) | ||
| 1817 | { | ||
| 1818 | id = this.Core.CreateIdentifier("reg", componentId, ((int)(root ?? RegistryRootType.Unknown)).ToString(), LowercaseOrNull(key), LowercaseOrNull(name)); | ||
| 1819 | } | ||
| 1820 | |||
| 1821 | if (RegistryValueType.MultiString != valueType && (RegistryValueActionType.Append == actionType || RegistryValueActionType.Prepend == actionType)) | ||
| 1822 | { | ||
| 1823 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString")); | ||
| 1824 | } | ||
| 1825 | |||
| 1826 | if (null == key) | ||
| 1827 | { | ||
| 1828 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 1829 | } | ||
| 1830 | |||
| 1831 | if (!root.HasValue) | ||
| 1832 | { | ||
| 1833 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); | ||
| 1834 | } | ||
| 1835 | |||
| 1836 | foreach (var child in node.Elements()) | ||
| 1837 | { | ||
| 1838 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1839 | { | ||
| 1840 | switch (child.Name.LocalName) | ||
| 1841 | { | ||
| 1842 | case "MultiString": | ||
| 1843 | case "MultiStringValue": | ||
| 1844 | if (RegistryValueType.MultiString != valueType && null != value) | ||
| 1845 | { | ||
| 1846 | this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); | ||
| 1847 | } | ||
| 1848 | else | ||
| 1849 | { | ||
| 1850 | value = this.ParseRegistryMultiStringElement(child, value); | ||
| 1851 | } | ||
| 1852 | break; | ||
| 1853 | case "Permission": | ||
| 1854 | this.ParsePermissionElement(child, id.Id, "Registry"); | ||
| 1855 | break; | ||
| 1856 | case "PermissionEx": | ||
| 1857 | this.ParsePermissionExElement(child, id.Id, "Registry"); | ||
| 1858 | break; | ||
| 1859 | default: | ||
| 1860 | this.Core.UnexpectedElement(node, child); | ||
| 1861 | break; | ||
| 1862 | } | ||
| 1863 | } | ||
| 1864 | else | ||
| 1865 | { | ||
| 1866 | var context = new Dictionary<string, string>() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; | ||
| 1867 | this.Core.ParseExtensionElement(node, child, context); | ||
| 1868 | } | ||
| 1869 | } | ||
| 1870 | |||
| 1871 | //switch (typeType) | ||
| 1872 | //{ | ||
| 1873 | //case Wix.RegistryValue.TypeType.binary: | ||
| 1874 | // value = String.Concat("#x", value); | ||
| 1875 | // break; | ||
| 1876 | //case Wix.RegistryValue.TypeType.expandable: | ||
| 1877 | // value = String.Concat("#%", value); | ||
| 1878 | // break; | ||
| 1879 | //case Wix.RegistryValue.TypeType.integer: | ||
| 1880 | // value = String.Concat("#", value); | ||
| 1881 | // break; | ||
| 1882 | //case Wix.RegistryValue.TypeType.multiString: | ||
| 1883 | // switch (actionType) | ||
| 1884 | // { | ||
| 1885 | // case Wix.RegistryValue.ActionType.append: | ||
| 1886 | // value = String.Concat("[~]", value); | ||
| 1887 | // break; | ||
| 1888 | // case Wix.RegistryValue.ActionType.prepend: | ||
| 1889 | // value = String.Concat(value, "[~]"); | ||
| 1890 | // break; | ||
| 1891 | // case Wix.RegistryValue.ActionType.write: | ||
| 1892 | // default: | ||
| 1893 | // if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) | ||
| 1894 | // { | ||
| 1895 | // value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); | ||
| 1896 | // } | ||
| 1897 | // break; | ||
| 1898 | // } | ||
| 1899 | // break; | ||
| 1900 | //case Wix.RegistryValue.TypeType.@string: | ||
| 1901 | // // escape the leading '#' character for string registry keys | ||
| 1902 | // if (null != value && value.StartsWith("#", StringComparison.Ordinal)) | ||
| 1903 | // { | ||
| 1904 | // value = String.Concat("#", value); | ||
| 1905 | // } | ||
| 1906 | // break; | ||
| 1907 | //} | ||
| 1908 | |||
| 1909 | // value may be set by child MultiStringValue elements, so it must be checked here | ||
| 1910 | if (null == value && valueType != RegistryValueType.Binary) | ||
| 1911 | { | ||
| 1912 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 1913 | } | ||
| 1914 | else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values | ||
| 1915 | { | ||
| 1916 | this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); | ||
| 1917 | } | ||
| 1918 | |||
| 1919 | if (!this.Core.EncounteredError) | ||
| 1920 | { | ||
| 1921 | this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) | ||
| 1922 | { | ||
| 1923 | Root = root.Value, | ||
| 1924 | Key = key, | ||
| 1925 | Name = name, | ||
| 1926 | Value = value, | ||
| 1927 | ValueType = valueType, | ||
| 1928 | ValueAction = actionType, | ||
| 1929 | ComponentRef = componentId, | ||
| 1930 | }); | ||
| 1931 | } | ||
| 1932 | |||
| 1933 | // If this was just a regular registry key (that could be the key path) | ||
| 1934 | // and no child registry key set the possible key path, let's make this | ||
| 1935 | // Registry/@Id a possible key path. | ||
| 1936 | if (null == possibleKeyPath) | ||
| 1937 | { | ||
| 1938 | possibleKeyPath = id.Id; | ||
| 1939 | } | ||
| 1940 | |||
| 1941 | return keyPath; | ||
| 1942 | } | ||
| 1943 | |||
| 1944 | private string ParseRegistryMultiStringElement(XElement node, string value) | ||
| 1945 | { | ||
| 1946 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1947 | string multiStringValue = null; | ||
| 1948 | |||
| 1949 | foreach (var attrib in node.Attributes()) | ||
| 1950 | { | ||
| 1951 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1952 | { | ||
| 1953 | switch (attrib.Name.LocalName) | ||
| 1954 | { | ||
| 1955 | case "Value": | ||
| 1956 | multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1957 | break; | ||
| 1958 | default: | ||
| 1959 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1960 | break; | ||
| 1961 | } | ||
| 1962 | } | ||
| 1963 | } | ||
| 1964 | |||
| 1965 | this.Core.ParseForExtensionElements(node); | ||
| 1966 | |||
| 1967 | return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue); | ||
| 1968 | } | ||
| 1969 | |||
| 1970 | /// <summary> | ||
| 1971 | /// Parses a RemoveRegistryKey element. | ||
| 1972 | /// </summary> | ||
| 1973 | /// <param name="node">The element to parse.</param> | ||
| 1974 | /// <param name="componentId">The component identifier of the parent element.</param> | ||
| 1975 | private void ParseRemoveRegistryKeyElement(XElement node, string componentId) | ||
| 1976 | { | ||
| 1977 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1978 | Identifier id = null; | ||
| 1979 | RemoveRegistryActionType? actionType = null; | ||
| 1980 | string key = null; | ||
| 1981 | var name = "-"; | ||
| 1982 | RegistryRootType? root = null; | ||
| 1983 | |||
| 1984 | foreach (var attrib in node.Attributes()) | ||
| 1985 | { | ||
| 1986 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1987 | { | ||
| 1988 | switch (attrib.Name.LocalName) | ||
| 1989 | { | ||
| 1990 | case "Id": | ||
| 1991 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1992 | break; | ||
| 1993 | case "Action": | ||
| 1994 | var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1995 | switch (actionValue) | ||
| 1996 | { | ||
| 1997 | case "removeOnInstall": | ||
| 1998 | actionType = RemoveRegistryActionType.RemoveOnInstall; | ||
| 1999 | break; | ||
| 2000 | case "removeOnUninstall": | ||
| 2001 | actionType = RemoveRegistryActionType.RemoveOnUninstall; | ||
| 2002 | break; | ||
| 2003 | case "": | ||
| 2004 | break; | ||
| 2005 | default: | ||
| 2006 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "removeOnInstall", "removeOnUninstall")); | ||
| 2007 | break; | ||
| 2008 | } | ||
| 2009 | //if (0 < action.Length) | ||
| 2010 | //{ | ||
| 2011 | // if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType)) | ||
| 2012 | // { | ||
| 2013 | // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall")); | ||
| 2014 | // } | ||
| 2015 | //} | ||
| 2016 | break; | ||
| 2017 | case "Key": | ||
| 2018 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2019 | break; | ||
| 2020 | case "Root": | ||
| 2021 | root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); | ||
| 2022 | break; | ||
| 2023 | default: | ||
| 2024 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2025 | break; | ||
| 2026 | } | ||
| 2027 | } | ||
| 2028 | else | ||
| 2029 | { | ||
| 2030 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2031 | } | ||
| 2032 | } | ||
| 2033 | |||
| 2034 | // generate the identifier if it wasn't provided | ||
| 2035 | if (null == id) | ||
| 2036 | { | ||
| 2037 | id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); | ||
| 2038 | } | ||
| 2039 | |||
| 2040 | if (!root.HasValue) | ||
| 2041 | { | ||
| 2042 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); | ||
| 2043 | } | ||
| 2044 | |||
| 2045 | if (null == key) | ||
| 2046 | { | ||
| 2047 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 2048 | } | ||
| 2049 | |||
| 2050 | if (!actionType.HasValue) | ||
| 2051 | { | ||
| 2052 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); | ||
| 2053 | } | ||
| 2054 | |||
| 2055 | this.Core.ParseForExtensionElements(node); | ||
| 2056 | |||
| 2057 | if (!this.Core.EncounteredError) | ||
| 2058 | { | ||
| 2059 | this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) | ||
| 2060 | { | ||
| 2061 | Root = root.Value, | ||
| 2062 | Key = key, | ||
| 2063 | Name = name, | ||
| 2064 | Action = actionType.Value, | ||
| 2065 | ComponentRef = componentId, | ||
| 2066 | }); | ||
| 2067 | } | ||
| 2068 | } | ||
| 2069 | |||
| 2070 | /// <summary> | ||
| 2071 | /// Parses a RemoveRegistryValue element. | ||
| 2072 | /// </summary> | ||
| 2073 | /// <param name="node">The element to parse.</param> | ||
| 2074 | /// <param name="componentId">The component identifier of the parent element.</param> | ||
| 2075 | private void ParseRemoveRegistryValueElement(XElement node, string componentId) | ||
| 2076 | { | ||
| 2077 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2078 | Identifier id = null; | ||
| 2079 | string key = null; | ||
| 2080 | string name = null; | ||
| 2081 | RegistryRootType? root = null; | ||
| 2082 | |||
| 2083 | foreach (var attrib in node.Attributes()) | ||
| 2084 | { | ||
| 2085 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2086 | { | ||
| 2087 | switch (attrib.Name.LocalName) | ||
| 2088 | { | ||
| 2089 | case "Id": | ||
| 2090 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2091 | break; | ||
| 2092 | case "Key": | ||
| 2093 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2094 | break; | ||
| 2095 | case "Name": | ||
| 2096 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2097 | break; | ||
| 2098 | case "Root": | ||
| 2099 | root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); | ||
| 2100 | break; | ||
| 2101 | default: | ||
| 2102 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2103 | break; | ||
| 2104 | } | ||
| 2105 | } | ||
| 2106 | else | ||
| 2107 | { | ||
| 2108 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2109 | } | ||
| 2110 | } | ||
| 2111 | |||
| 2112 | // generate the identifier if it wasn't provided | ||
| 2113 | if (null == id) | ||
| 2114 | { | ||
| 2115 | id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); | ||
| 2116 | } | ||
| 2117 | |||
| 2118 | if (!root.HasValue) | ||
| 2119 | { | ||
| 2120 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); | ||
| 2121 | } | ||
| 2122 | |||
| 2123 | if (null == key) | ||
| 2124 | { | ||
| 2125 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 2126 | } | ||
| 2127 | |||
| 2128 | this.Core.ParseForExtensionElements(node); | ||
| 2129 | |||
| 2130 | if (!this.Core.EncounteredError) | ||
| 2131 | { | ||
| 2132 | this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) | ||
| 2133 | { | ||
| 2134 | Root = root.Value, | ||
| 2135 | Key = key, | ||
| 2136 | Name = name, | ||
| 2137 | ComponentRef = componentId | ||
| 2138 | }); | ||
| 2139 | } | ||
| 2140 | } | ||
| 2141 | |||
| 2142 | /// <summary> | ||
| 2143 | /// Parses a remove file element. | ||
| 2144 | /// </summary> | ||
| 2145 | /// <param name="node">Element to parse.</param> | ||
| 2146 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 2147 | /// <param name="parentDirectory">Identifier of the parent component's directory.</param> | ||
| 2148 | private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory) | ||
| 2149 | { | ||
| 2150 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2151 | Identifier id = null; | ||
| 2152 | string directoryId = null; | ||
| 2153 | string subdirectory = null; | ||
| 2154 | string name = null; | ||
| 2155 | bool? onInstall = null; | ||
| 2156 | bool? onUninstall = null; | ||
| 2157 | string propertyId = null; | ||
| 2158 | string shortName = null; | ||
| 2159 | |||
| 2160 | foreach (var attrib in node.Attributes()) | ||
| 2161 | { | ||
| 2162 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2163 | { | ||
| 2164 | switch (attrib.Name.LocalName) | ||
| 2165 | { | ||
| 2166 | case "Id": | ||
| 2167 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2168 | break; | ||
| 2169 | case "Directory": | ||
| 2170 | directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2171 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); | ||
| 2172 | break; | ||
| 2173 | case "Subdirectory": | ||
| 2174 | subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 2175 | break; | ||
| 2176 | case "Name": | ||
| 2177 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); | ||
| 2178 | break; | ||
| 2179 | case "On": | ||
| 2180 | var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2181 | switch (onValue) | ||
| 2182 | { | ||
| 2183 | case "install": | ||
| 2184 | onInstall = true; | ||
| 2185 | break; | ||
| 2186 | case "uninstall": | ||
| 2187 | onUninstall = true; | ||
| 2188 | break; | ||
| 2189 | case "both": | ||
| 2190 | onInstall = true; | ||
| 2191 | onUninstall = true; | ||
| 2192 | break; | ||
| 2193 | } | ||
| 2194 | break; | ||
| 2195 | case "Property": | ||
| 2196 | propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2197 | break; | ||
| 2198 | case "ShortName": | ||
| 2199 | shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); | ||
| 2200 | break; | ||
| 2201 | default: | ||
| 2202 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2203 | break; | ||
| 2204 | } | ||
| 2205 | } | ||
| 2206 | else | ||
| 2207 | { | ||
| 2208 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2209 | } | ||
| 2210 | } | ||
| 2211 | |||
| 2212 | if (null == name) | ||
| 2213 | { | ||
| 2214 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 2215 | } | ||
| 2216 | |||
| 2217 | if (!onInstall.HasValue && !onUninstall.HasValue) | ||
| 2218 | { | ||
| 2219 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); | ||
| 2220 | } | ||
| 2221 | |||
| 2222 | if (String.IsNullOrEmpty(propertyId)) | ||
| 2223 | { | ||
| 2224 | directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); | ||
| 2225 | } | ||
| 2226 | else if (!String.IsNullOrEmpty(directoryId)) | ||
| 2227 | { | ||
| 2228 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); | ||
| 2229 | } | ||
| 2230 | else if (!String.IsNullOrEmpty(subdirectory)) | ||
| 2231 | { | ||
| 2232 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); | ||
| 2233 | } | ||
| 2234 | |||
| 2235 | if (null == id) | ||
| 2236 | { | ||
| 2237 | var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; | ||
| 2238 | id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); | ||
| 2239 | } | ||
| 2240 | |||
| 2241 | this.Core.ParseForExtensionElements(node); | ||
| 2242 | |||
| 2243 | if (!this.Core.EncounteredError) | ||
| 2244 | { | ||
| 2245 | this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) | ||
| 2246 | { | ||
| 2247 | ComponentRef = componentId, | ||
| 2248 | FileName = name, | ||
| 2249 | ShortFileName = shortName, | ||
| 2250 | DirPropertyRef = directoryId ?? propertyId ?? parentDirectory, | ||
| 2251 | OnInstall = onInstall, | ||
| 2252 | OnUninstall = onUninstall, | ||
| 2253 | }); | ||
| 2254 | } | ||
| 2255 | } | ||
| 2256 | |||
| 2257 | /// <summary> | ||
| 2258 | /// Parses a RemoveFolder element. | ||
| 2259 | /// </summary> | ||
| 2260 | /// <param name="node">Element to parse.</param> | ||
| 2261 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 2262 | /// <param name="parentDirectory">Identifier of parent component's directory.</param> | ||
| 2263 | private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory) | ||
| 2264 | { | ||
| 2265 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2266 | Identifier id = null; | ||
| 2267 | string directoryId = null; | ||
| 2268 | string subdirectory = null; | ||
| 2269 | bool? onInstall = null; | ||
| 2270 | bool? onUninstall = null; | ||
| 2271 | string propertyId = null; | ||
| 2272 | |||
| 2273 | foreach (var attrib in node.Attributes()) | ||
| 2274 | { | ||
| 2275 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2276 | { | ||
| 2277 | switch (attrib.Name.LocalName) | ||
| 2278 | { | ||
| 2279 | case "Id": | ||
| 2280 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2281 | break; | ||
| 2282 | case "Directory": | ||
| 2283 | directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2284 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); | ||
| 2285 | break; | ||
| 2286 | case "Subdirectory": | ||
| 2287 | subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 2288 | break; | ||
| 2289 | case "On": | ||
| 2290 | var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2291 | switch (onValue) | ||
| 2292 | { | ||
| 2293 | case "install": | ||
| 2294 | onInstall = true; | ||
| 2295 | break; | ||
| 2296 | case "uninstall": | ||
| 2297 | onUninstall = true; | ||
| 2298 | break; | ||
| 2299 | case "both": | ||
| 2300 | onInstall = true; | ||
| 2301 | onUninstall = true; | ||
| 2302 | break; | ||
| 2303 | } | ||
| 2304 | break; | ||
| 2305 | case "Property": | ||
| 2306 | propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2307 | break; | ||
| 2308 | default: | ||
| 2309 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2310 | break; | ||
| 2311 | } | ||
| 2312 | } | ||
| 2313 | else | ||
| 2314 | { | ||
| 2315 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2316 | } | ||
| 2317 | } | ||
| 2318 | |||
| 2319 | if (!onInstall.HasValue && !onUninstall.HasValue) | ||
| 2320 | { | ||
| 2321 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); | ||
| 2322 | } | ||
| 2323 | |||
| 2324 | if (String.IsNullOrEmpty(propertyId)) | ||
| 2325 | { | ||
| 2326 | directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); | ||
| 2327 | } | ||
| 2328 | else if (!String.IsNullOrEmpty(directoryId)) | ||
| 2329 | { | ||
| 2330 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); | ||
| 2331 | } | ||
| 2332 | else if (!String.IsNullOrEmpty(subdirectory)) | ||
| 2333 | { | ||
| 2334 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); | ||
| 2335 | } | ||
| 2336 | |||
| 2337 | if (null == id) | ||
| 2338 | { | ||
| 2339 | var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; | ||
| 2340 | id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId, on.ToString()); | ||
| 2341 | } | ||
| 2342 | |||
| 2343 | this.Core.ParseForExtensionElements(node); | ||
| 2344 | |||
| 2345 | if (!this.Core.EncounteredError) | ||
| 2346 | { | ||
| 2347 | this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) | ||
| 2348 | { | ||
| 2349 | ComponentRef = componentId, | ||
| 2350 | DirPropertyRef = directoryId ?? propertyId, | ||
| 2351 | OnInstall = onInstall, | ||
| 2352 | OnUninstall = onUninstall | ||
| 2353 | }); | ||
| 2354 | } | ||
| 2355 | } | ||
| 2356 | |||
| 2357 | /// <summary> | ||
| 2358 | /// Parses a reserve cost element. | ||
| 2359 | /// </summary> | ||
| 2360 | /// <param name="node">Element to parse.</param> | ||
| 2361 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 2362 | /// <param name="directoryId">Optional and default identifier of referenced directory.</param> | ||
| 2363 | private void ParseReserveCostElement(XElement node, string componentId, string directoryId) | ||
| 2364 | { | ||
| 2365 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2366 | Identifier id = null; | ||
| 2367 | string subdirectory = null; | ||
| 2368 | var runFromSource = CompilerConstants.IntegerNotSet; | ||
| 2369 | var runLocal = CompilerConstants.IntegerNotSet; | ||
| 2370 | |||
| 2371 | foreach (var attrib in node.Attributes()) | ||
| 2372 | { | ||
| 2373 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2374 | { | ||
| 2375 | switch (attrib.Name.LocalName) | ||
| 2376 | { | ||
| 2377 | case "Id": | ||
| 2378 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2379 | break; | ||
| 2380 | case "Directory": | ||
| 2381 | directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 2382 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); | ||
| 2383 | break; | ||
| 2384 | case "Subdirectory": | ||
| 2385 | subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 2386 | break; | ||
| 2387 | case "RunFromSource": | ||
| 2388 | runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 2389 | break; | ||
| 2390 | case "RunLocal": | ||
| 2391 | runLocal = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 2392 | break; | ||
| 2393 | default: | ||
| 2394 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2395 | break; | ||
| 2396 | } | ||
| 2397 | } | ||
| 2398 | else | ||
| 2399 | { | ||
| 2400 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2401 | } | ||
| 2402 | } | ||
| 2403 | |||
| 2404 | directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); | ||
| 2405 | |||
| 2406 | if (null == id) | ||
| 2407 | { | ||
| 2408 | id = this.Core.CreateIdentifier("rc", componentId, directoryId); | ||
| 2409 | } | ||
| 2410 | |||
| 2411 | if (CompilerConstants.IntegerNotSet == runFromSource) | ||
| 2412 | { | ||
| 2413 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource")); | ||
| 2414 | } | ||
| 2415 | |||
| 2416 | if (CompilerConstants.IntegerNotSet == runLocal) | ||
| 2417 | { | ||
| 2418 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal")); | ||
| 2419 | } | ||
| 2420 | |||
| 2421 | this.Core.ParseForExtensionElements(node); | ||
| 2422 | |||
| 2423 | if (!this.Core.EncounteredError) | ||
| 2424 | { | ||
| 2425 | this.Core.AddSymbol(new ReserveCostSymbol(sourceLineNumbers, id) | ||
| 2426 | { | ||
| 2427 | ComponentRef = componentId, | ||
| 2428 | ReserveFolder = directoryId, | ||
| 2429 | ReserveLocal = runLocal, | ||
| 2430 | ReserveSource = runFromSource | ||
| 2431 | }); | ||
| 2432 | } | ||
| 2433 | } | ||
| 2434 | |||
| 2435 | /// <summary> | ||
| 2436 | /// Parses a sequence element. | ||
| 2437 | /// </summary> | ||
| 2438 | /// <param name="node">Element to parse.</param> | ||
| 2439 | /// <param name="sequenceTable">Name of sequence table.</param> | ||
| 2440 | private void ParseSequenceElement(XElement node, SequenceTable sequenceTable) | ||
| 2441 | { | ||
| 2442 | // Parse each action in the sequence. | ||
| 2443 | foreach (var child in node.Elements()) | ||
| 2444 | { | ||
| 2445 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 2446 | var actionName = child.Name.LocalName; | ||
| 2447 | string afterAction = null; | ||
| 2448 | string beforeAction = null; | ||
| 2449 | string condition = null; | ||
| 2450 | var customAction = "Custom" == actionName; | ||
| 2451 | var overridable = false; | ||
| 2452 | var exitSequence = CompilerConstants.IntegerNotSet; | ||
| 2453 | var sequence = CompilerConstants.IntegerNotSet; | ||
| 2454 | var showDialog = "Show" == actionName; | ||
| 2455 | var specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName; | ||
| 2456 | var specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName; | ||
| 2457 | var suppress = false; | ||
| 2458 | |||
| 2459 | foreach (var attrib in child.Attributes()) | ||
| 2460 | { | ||
| 2461 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2462 | { | ||
| 2463 | switch (attrib.Name.LocalName) | ||
| 2464 | { | ||
| 2465 | case "Action": | ||
| 2466 | if (customAction) | ||
| 2467 | { | ||
| 2468 | actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); | ||
| 2469 | this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.CustomAction, actionName); | ||
| 2470 | } | ||
| 2471 | else | ||
| 2472 | { | ||
| 2473 | this.Core.UnexpectedAttribute(child, attrib); | ||
| 2474 | } | ||
| 2475 | break; | ||
| 2476 | case "After": | ||
| 2477 | if (customAction || showDialog || specialAction || specialStandardAction) | ||
| 2478 | { | ||
| 2479 | afterAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); | ||
| 2480 | this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), afterAction); | ||
| 2481 | } | ||
| 2482 | else | ||
| 2483 | { | ||
| 2484 | this.Core.UnexpectedAttribute(child, attrib); | ||
| 2485 | } | ||
| 2486 | break; | ||
| 2487 | case "Before": | ||
| 2488 | if (customAction || showDialog || specialAction || specialStandardAction) | ||
| 2489 | { | ||
| 2490 | beforeAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); | ||
| 2491 | this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), beforeAction); | ||
| 2492 | } | ||
| 2493 | else | ||
| 2494 | { | ||
| 2495 | this.Core.UnexpectedAttribute(child, attrib); | ||
| 2496 | } | ||
| 2497 | break; | ||
| 2498 | case "Condition": | ||
| 2499 | condition = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); | ||
| 2500 | break; | ||
| 2501 | case "Dialog": | ||
| 2502 | if (showDialog) | ||
| 2503 | { | ||
| 2504 | actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); | ||
| 2505 | this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.Dialog, actionName); | ||
| 2506 | } | ||
| 2507 | else | ||
| 2508 | { | ||
| 2509 | this.Core.UnexpectedAttribute(child, attrib); | ||
| 2510 | } | ||
| 2511 | break; | ||
| 2512 | case "OnExit": | ||
| 2513 | if (customAction || showDialog || specialAction) | ||
| 2514 | { | ||
| 2515 | var exitValue = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); | ||
| 2516 | switch (exitValue) | ||
| 2517 | { | ||
| 2518 | case "success": | ||
| 2519 | exitSequence = -1; | ||
| 2520 | break; | ||
| 2521 | case "cancel": | ||
| 2522 | exitSequence = -2; | ||
| 2523 | break; | ||
| 2524 | case "error": | ||
| 2525 | exitSequence = -3; | ||
| 2526 | break; | ||
| 2527 | case "suspend": | ||
| 2528 | exitSequence = -4; | ||
| 2529 | break; | ||
| 2530 | } | ||
| 2531 | } | ||
| 2532 | else | ||
| 2533 | { | ||
| 2534 | this.Core.UnexpectedAttribute(child, attrib); | ||
| 2535 | } | ||
| 2536 | break; | ||
| 2537 | case "Overridable": | ||
| 2538 | overridable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); | ||
| 2539 | break; | ||
| 2540 | case "Sequence": | ||
| 2541 | sequence = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 2542 | break; | ||
| 2543 | case "Suppress": | ||
| 2544 | suppress = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); | ||
| 2545 | break; | ||
| 2546 | default: | ||
| 2547 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2548 | break; | ||
| 2549 | } | ||
| 2550 | } | ||
| 2551 | else | ||
| 2552 | { | ||
| 2553 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2554 | } | ||
| 2555 | } | ||
| 2556 | |||
| 2557 | if (customAction && "Custom" == actionName) | ||
| 2558 | { | ||
| 2559 | this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); | ||
| 2560 | } | ||
| 2561 | else if (showDialog && "Show" == actionName) | ||
| 2562 | { | ||
| 2563 | this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog")); | ||
| 2564 | } | ||
| 2565 | |||
| 2566 | if (CompilerConstants.IntegerNotSet != sequence) | ||
| 2567 | { | ||
| 2568 | if (CompilerConstants.IntegerNotSet != exitSequence) | ||
| 2569 | { | ||
| 2570 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit")); | ||
| 2571 | } | ||
| 2572 | else if (null != beforeAction || null != afterAction) | ||
| 2573 | { | ||
| 2574 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After")); | ||
| 2575 | } | ||
| 2576 | } | ||
| 2577 | else // sequence not specified use OnExit (which may also be not set). | ||
| 2578 | { | ||
| 2579 | sequence = exitSequence; | ||
| 2580 | } | ||
| 2581 | |||
| 2582 | if (null != beforeAction && null != afterAction) | ||
| 2583 | { | ||
| 2584 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before")); | ||
| 2585 | } | ||
| 2586 | else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction) | ||
| 2587 | { | ||
| 2588 | this.Core.Write(ErrorMessages.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName)); | ||
| 2589 | } | ||
| 2590 | |||
| 2591 | // action that is scheduled to occur before/after itself | ||
| 2592 | if (beforeAction == actionName) | ||
| 2593 | { | ||
| 2594 | this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction)); | ||
| 2595 | } | ||
| 2596 | else if (afterAction == actionName) | ||
| 2597 | { | ||
| 2598 | this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction)); | ||
| 2599 | } | ||
| 2600 | |||
| 2601 | // normal standard actions cannot be set overridable by the user (since they are overridable by default) | ||
| 2602 | if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction) | ||
| 2603 | { | ||
| 2604 | this.Core.Write(ErrorMessages.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable")); | ||
| 2605 | } | ||
| 2606 | |||
| 2607 | // suppress cannot be specified at the same time as Before, After, or Sequence | ||
| 2608 | if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable)) | ||
| 2609 | { | ||
| 2610 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable")); | ||
| 2611 | } | ||
| 2612 | |||
| 2613 | this.Core.ParseForExtensionElements(child); | ||
| 2614 | |||
| 2615 | // add the row and any references needed | ||
| 2616 | if (!this.Core.EncounteredError) | ||
| 2617 | { | ||
| 2618 | if (suppress) | ||
| 2619 | { | ||
| 2620 | this.Core.AddSymbol(new WixSuppressActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) | ||
| 2621 | { | ||
| 2622 | SequenceTable = sequenceTable, | ||
| 2623 | Action = actionName | ||
| 2624 | }); | ||
| 2625 | } | ||
| 2626 | else | ||
| 2627 | { | ||
| 2628 | var symbol = this.Core.AddSymbol(new WixActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) | ||
| 2629 | { | ||
| 2630 | SequenceTable = sequenceTable, | ||
| 2631 | Action = actionName, | ||
| 2632 | Condition = condition, | ||
| 2633 | Before = beforeAction, | ||
| 2634 | After = afterAction, | ||
| 2635 | Overridable = overridable, | ||
| 2636 | }); | ||
| 2637 | |||
| 2638 | if (CompilerConstants.IntegerNotSet != sequence) | ||
| 2639 | { | ||
| 2640 | symbol.Sequence = sequence; | ||
| 2641 | } | ||
| 2642 | } | ||
| 2643 | } | ||
| 2644 | } | ||
| 2645 | |||
| 2646 | this.Core.InnerTextDisallowed(node); | ||
| 2647 | } | ||
| 2648 | |||
| 2649 | |||
| 2650 | /// <summary> | ||
| 2651 | /// Parses a service config element. | ||
| 2652 | /// </summary> | ||
| 2653 | /// <param name="node">Element to parse.</param> | ||
| 2654 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 2655 | /// <param name="serviceName">Optional element containing parent's service name.</param> | ||
| 2656 | private void ParseServiceConfigElement(XElement node, string componentId, string serviceName) | ||
| 2657 | { | ||
| 2658 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2659 | Identifier id = null; | ||
| 2660 | string delayedAutoStart = null; | ||
| 2661 | string failureActionsWhen = null; | ||
| 2662 | var name = serviceName; | ||
| 2663 | var install = false; | ||
| 2664 | var reinstall = false; | ||
| 2665 | var uninstall = false; | ||
| 2666 | string preShutdownDelay = null; | ||
| 2667 | string requiredPrivileges = null; | ||
| 2668 | string sid = null; | ||
| 2669 | |||
| 2670 | this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); | ||
| 2671 | |||
| 2672 | foreach (var attrib in node.Attributes()) | ||
| 2673 | { | ||
| 2674 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2675 | { | ||
| 2676 | switch (attrib.Name.LocalName) | ||
| 2677 | { | ||
| 2678 | case "Id": | ||
| 2679 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 2680 | break; | ||
| 2681 | case "DelayedAutoStart": | ||
| 2682 | delayedAutoStart = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2683 | switch (delayedAutoStart) | ||
| 2684 | { | ||
| 2685 | case "no": | ||
| 2686 | delayedAutoStart = "0"; | ||
| 2687 | break; | ||
| 2688 | case "yes": | ||
| 2689 | delayedAutoStart = "1"; | ||
| 2690 | break; | ||
| 2691 | default: | ||
| 2692 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
| 2693 | break; | ||
| 2694 | } | ||
| 2695 | break; | ||
| 2696 | case "FailureActionsWhen": | ||
| 2697 | failureActionsWhen = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2698 | switch (failureActionsWhen) | ||
| 2699 | { | ||
| 2700 | case "failedToStop": | ||
| 2701 | failureActionsWhen = "0"; | ||
| 2702 | break; | ||
| 2703 | case "failedToStopOrReturnedError": | ||
| 2704 | failureActionsWhen = "1"; | ||
| 2705 | break; | ||
| 2706 | default: | ||
| 2707 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
| 2708 | break; | ||
| 2709 | } | ||
| 2710 | break; | ||
| 2711 | case "OnInstall": | ||
| 2712 | install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2713 | //if (YesNoType.Yes == install) | ||
| 2714 | //{ | ||
| 2715 | // events |= MsiInterop.MsidbServiceConfigEventInstall; | ||
| 2716 | //} | ||
| 2717 | break; | ||
| 2718 | case "OnReinstall": | ||
| 2719 | reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2720 | //if (YesNoType.Yes == reinstall) | ||
| 2721 | //{ | ||
| 2722 | // events |= MsiInterop.MsidbServiceConfigEventReinstall; | ||
| 2723 | //} | ||
| 2724 | break; | ||
| 2725 | case "OnUninstall": | ||
| 2726 | uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 2727 | //if (YesNoType.Yes == uninstall) | ||
| 2728 | //{ | ||
| 2729 | // events |= MsiInterop.MsidbServiceConfigEventUninstall; | ||
| 2730 | //} | ||
| 2731 | break; | ||
| 2732 | default: | ||
| 2733 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 2734 | break; | ||
| 2735 | case "PreShutdownDelay": | ||
| 2736 | preShutdownDelay = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 2737 | break; | ||
| 2738 | case "ServiceName": | ||
| 2739 | if (!String.IsNullOrEmpty(serviceName)) | ||
| 2740 | { | ||
| 2741 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); | ||
| 2742 | } | ||
| 2743 | |||
| 2744 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2745 | break; | ||
| 2746 | case "ServiceSid": | ||
| 2747 | sid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2748 | switch (sid) | ||
| 2749 | { | ||
| 2750 | case "none": | ||
| 2751 | sid = "0"; | ||
| 2752 | break; | ||
| 2753 | case "restricted": | ||
| 2754 | sid = "3"; | ||
| 2755 | break; | ||
| 2756 | case "unrestricted": | ||
| 2757 | sid = "1"; | ||
| 2758 | break; | ||
| 2759 | default: | ||
| 2760 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
| 2761 | break; | ||
| 2762 | } | ||
| 2763 | break; | ||
| 2764 | } | ||
| 2765 | } | ||
| 2766 | else | ||
| 2767 | { | ||
| 2768 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 2769 | } | ||
| 2770 | } | ||
| 2771 | |||
| 2772 | // Get the ServiceConfig required privilegs. | ||
| 2773 | foreach (var child in node.Elements()) | ||
| 2774 | { | ||
| 2775 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 2776 | { | ||
| 2777 | switch (child.Name.LocalName) | ||
| 2778 | { | ||
| 2779 | case "RequiredPrivilege": | ||
| 2780 | requiredPrivileges = this.ParseRequiredPrivilege(child, requiredPrivileges); | ||
| 2781 | break; | ||
| 2782 | default: | ||
| 2783 | this.Core.UnexpectedElement(node, child); | ||
| 2784 | break; | ||
| 2785 | } | ||
| 2786 | } | ||
| 2787 | else | ||
| 2788 | { | ||
| 2789 | this.Core.ParseExtensionElement(node, child); | ||
| 2790 | } | ||
| 2791 | } | ||
| 2792 | |||
| 2793 | if (String.IsNullOrEmpty(name)) | ||
| 2794 | { | ||
| 2795 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); | ||
| 2796 | } | ||
| 2797 | else if (null == id) | ||
| 2798 | { | ||
| 2799 | id = this.Core.CreateIdentifierFromFilename(name); | ||
| 2800 | } | ||
| 2801 | |||
| 2802 | if (!install && !reinstall && !uninstall) | ||
| 2803 | { | ||
| 2804 | this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); | ||
| 2805 | } | ||
| 2806 | |||
| 2807 | if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid)) | ||
| 2808 | { | ||
| 2809 | this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege")); | ||
| 2810 | } | ||
| 2811 | |||
| 2812 | if (!this.Core.EncounteredError) | ||
| 2813 | { | ||
| 2814 | if (!String.IsNullOrEmpty(delayedAutoStart)) | ||
| 2815 | { | ||
| 2816 | this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".DS"))) | ||
| 2817 | { | ||
| 2818 | Name = name, | ||
| 2819 | OnInstall = install, | ||
| 2820 | OnReinstall = reinstall, | ||
| 2821 | OnUninstall = uninstall, | ||
| 2822 | ConfigType = MsiServiceConfigType.DelayedAutoStart, | ||
| 2823 | Argument = delayedAutoStart, | ||
| 2824 | ComponentRef = componentId, | ||
| 2825 | }); | ||
| 2826 | } | ||
| 2827 | |||
| 2828 | if (!String.IsNullOrEmpty(failureActionsWhen)) | ||
| 2829 | { | ||
| 2830 | this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".FA"))) | ||
| 2831 | { | ||
| 2832 | Name = name, | ||
| 2833 | OnInstall = install, | ||
| 2834 | OnReinstall = reinstall, | ||
| 2835 | OnUninstall = uninstall, | ||
| 2836 | ConfigType = MsiServiceConfigType.FailureActionsFlag, | ||
| 2837 | Argument = failureActionsWhen, | ||
| 2838 | ComponentRef = componentId, | ||
| 2839 | }); | ||
| 2840 | } | ||
| 2841 | |||
| 2842 | if (!String.IsNullOrEmpty(sid)) | ||
| 2843 | { | ||
| 2844 | this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".SS"))) | ||
| 2845 | { | ||
| 2846 | Name = name, | ||
| 2847 | OnInstall = install, | ||
| 2848 | OnReinstall = reinstall, | ||
| 2849 | OnUninstall = uninstall, | ||
| 2850 | ConfigType = MsiServiceConfigType.ServiceSidInfo, | ||
| 2851 | Argument = sid, | ||
| 2852 | ComponentRef = componentId, | ||
| 2853 | }); | ||
| 2854 | } | ||
| 2855 | |||
| 2856 | if (!String.IsNullOrEmpty(requiredPrivileges)) | ||
| 2857 | { | ||
| 2858 | this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".RP"))) | ||
| 2859 | { | ||
| 2860 | Name = name, | ||
| 2861 | OnInstall = install, | ||
| 2862 | OnReinstall = reinstall, | ||
| 2863 | OnUninstall = uninstall, | ||
| 2864 | ConfigType = MsiServiceConfigType.RequiredPrivilegesInfo, | ||
| 2865 | Argument = requiredPrivileges, | ||
| 2866 | ComponentRef = componentId, | ||
| 2867 | }); | ||
| 2868 | } | ||
| 2869 | |||
| 2870 | if (!String.IsNullOrEmpty(preShutdownDelay)) | ||
| 2871 | { | ||
| 2872 | this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".PD"))) | ||
| 2873 | { | ||
| 2874 | Name = name, | ||
| 2875 | OnInstall = install, | ||
| 2876 | OnReinstall = reinstall, | ||
| 2877 | OnUninstall = uninstall, | ||
| 2878 | ConfigType = MsiServiceConfigType.PreshutdownInfo, | ||
| 2879 | Argument = preShutdownDelay, | ||
| 2880 | ComponentRef = componentId, | ||
| 2881 | }); | ||
| 2882 | } | ||
| 2883 | } | ||
| 2884 | } | ||
| 2885 | |||
| 2886 | private string ParseRequiredPrivilege(XElement node, string requiredPrivileges) | ||
| 2887 | { | ||
| 2888 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 2889 | string privilege = null; | ||
| 2890 | |||
| 2891 | foreach (var attrib in node.Attributes()) | ||
| 2892 | { | ||
| 2893 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 2894 | { | ||
| 2895 | switch (attrib.Name.LocalName) | ||
| 2896 | { | ||
| 2897 | case "Name": | ||
| 2898 | privilege = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 2899 | switch (privilege) | ||
| 2900 | { | ||
| 2901 | case "assignPrimaryToken": | ||
| 2902 | privilege = "SeAssignPrimaryTokenPrivilege"; | ||
| 2903 | break; | ||
| 2904 | case "audit": | ||
| 2905 | privilege = "SeAuditPrivilege"; | ||
| 2906 | break; | ||
| 2907 | case "backup": | ||
| 2908 | privilege = "SeBackupPrivilege"; | ||
| 2909 | break; | ||
| 2910 | case "changeNotify": | ||
| 2911 | privilege = "SeChangeNotifyPrivilege"; | ||
| 2912 | break; | ||
| 2913 | case "createGlobal": | ||
| 2914 | privilege = "SeCreateGlobalPrivilege"; | ||
| 2915 | break; | ||
| 2916 | case "createPagefile": | ||
| 2917 | privilege = "SeCreatePagefilePrivilege"; | ||
| 2918 | break; | ||
| 2919 | case "createPermanent": | ||
| 2920 | privilege = "SeCreatePermanentPrivilege"; | ||
| 2921 | break; | ||
| 2922 | case "createSymbolicLink": | ||
| 2923 | privilege = "SeCreateSymbolicLinkPrivilege"; | ||
| 2924 | break; | ||
| 2925 | case "createToken": | ||
| 2926 | privilege = "SeCreateTokenPrivilege"; | ||
| 2927 | break; | ||
| 2928 | case "debug": | ||
| 2929 | privilege = "SeDebugPrivilege"; | ||
| 2930 | break; | ||
| 2931 | case "enableDelegation": | ||
| 2932 | privilege = "SeEnableDelegationPrivilege"; | ||
| 2933 | break; | ||
| 2934 | case "impersonate": | ||
| 2935 | privilege = "SeImpersonatePrivilege"; | ||
| 2936 | break; | ||
| 2937 | case "increaseBasePriority": | ||
| 2938 | privilege = "SeIncreaseBasePriorityPrivilege"; | ||
| 2939 | break; | ||
| 2940 | case "increaseQuota": | ||
| 2941 | privilege = "SeIncreaseQuotaPrivilege"; | ||
| 2942 | break; | ||
| 2943 | case "increaseWorkingSet": | ||
| 2944 | privilege = "SeIncreaseWorkingSetPrivilege"; | ||
| 2945 | break; | ||
| 2946 | case "loadDriver": | ||
| 2947 | privilege = "SeLoadDriverPrivilege"; | ||
| 2948 | break; | ||
| 2949 | case "lockMemory": | ||
| 2950 | privilege = "SeLockMemoryPrivilege"; | ||
| 2951 | break; | ||
| 2952 | case "machineAccount": | ||
| 2953 | privilege = "SeMachineAccountPrivilege"; | ||
| 2954 | break; | ||
| 2955 | case "manageVolume": | ||
| 2956 | privilege = "SeManageVolumePrivilege"; | ||
| 2957 | break; | ||
| 2958 | case "profileSingleProcess": | ||
| 2959 | privilege = "SeProfileSingleProcessPrivilege"; | ||
| 2960 | break; | ||
| 2961 | case "relabel": | ||
| 2962 | privilege = "SeRelabelPrivilege"; | ||
| 2963 | break; | ||
| 2964 | case "remoteShutdown": | ||
| 2965 | privilege = "SeRemoteShutdownPrivilege"; | ||
| 2966 | break; | ||
| 2967 | case "restore": | ||
| 2968 | privilege = "SeRestorePrivilege"; | ||
| 2969 | break; | ||
| 2970 | case "security": | ||
| 2971 | privilege = "SeSecurityPrivilege"; | ||
| 2972 | break; | ||
| 2973 | case "shutdown": | ||
| 2974 | privilege = "SeShutdownPrivilege"; | ||
| 2975 | break; | ||
| 2976 | case "syncAgent": | ||
| 2977 | privilege = "SeSyncAgentPrivilege"; | ||
| 2978 | break; | ||
| 2979 | case "systemEnvironment": | ||
| 2980 | privilege = "SeSystemEnvironmentPrivilege"; | ||
| 2981 | break; | ||
| 2982 | case "systemProfile": | ||
| 2983 | privilege = "SeSystemProfilePrivilege"; | ||
| 2984 | break; | ||
| 2985 | case "systemTime": | ||
| 2986 | case "modifySystemTime": | ||
| 2987 | privilege = "SeSystemtimePrivilege"; | ||
| 2988 | break; | ||
| 2989 | case "takeOwnership": | ||
| 2990 | privilege = "SeTakeOwnershipPrivilege"; | ||
| 2991 | break; | ||
| 2992 | case "tcb": | ||
| 2993 | case "trustedComputerBase": | ||
| 2994 | privilege = "SeTcbPrivilege"; | ||
| 2995 | break; | ||
| 2996 | case "timeZone": | ||
| 2997 | case "modifyTimeZone": | ||
| 2998 | privilege = "SeTimeZonePrivilege"; | ||
| 2999 | break; | ||
| 3000 | case "trustedCredManAccess": | ||
| 3001 | case "trustedCredentialManagerAccess": | ||
| 3002 | privilege = "SeTrustedCredManAccessPrivilege"; | ||
| 3003 | break; | ||
| 3004 | case "undock": | ||
| 3005 | privilege = "SeUndockPrivilege"; | ||
| 3006 | break; | ||
| 3007 | case "unsolicitedInput": | ||
| 3008 | privilege = "SeUnsolicitedInputPrivilege"; | ||
| 3009 | break; | ||
| 3010 | default: | ||
| 3011 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
| 3012 | break; | ||
| 3013 | } | ||
| 3014 | break; | ||
| 3015 | default: | ||
| 3016 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3017 | break; | ||
| 3018 | } | ||
| 3019 | } | ||
| 3020 | } | ||
| 3021 | |||
| 3022 | if (privilege == null) | ||
| 3023 | { | ||
| 3024 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 3025 | } | ||
| 3026 | |||
| 3027 | this.Core.ParseForExtensionElements(node); | ||
| 3028 | |||
| 3029 | return (requiredPrivileges == null) ? privilege : String.Concat(requiredPrivileges, "[~]", privilege); | ||
| 3030 | } | ||
| 3031 | |||
| 3032 | /// <summary> | ||
| 3033 | /// Parses a service config failure actions element. | ||
| 3034 | /// </summary> | ||
| 3035 | /// <param name="node">Element to parse.</param> | ||
| 3036 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 3037 | /// <param name="serviceName">Optional element containing parent's service name.</param> | ||
| 3038 | private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName) | ||
| 3039 | { | ||
| 3040 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3041 | Identifier id = null; | ||
| 3042 | var name = serviceName; | ||
| 3043 | var install = false; | ||
| 3044 | var reinstall = false; | ||
| 3045 | var uninstall = false; | ||
| 3046 | int? resetPeriod = null; | ||
| 3047 | string rebootMessage = null; | ||
| 3048 | string command = null; | ||
| 3049 | string actions = null; | ||
| 3050 | string actionsDelays = null; | ||
| 3051 | |||
| 3052 | this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); | ||
| 3053 | |||
| 3054 | foreach (var attrib in node.Attributes()) | ||
| 3055 | { | ||
| 3056 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3057 | { | ||
| 3058 | switch (attrib.Name.LocalName) | ||
| 3059 | { | ||
| 3060 | case "Id": | ||
| 3061 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 3062 | break; | ||
| 3063 | case "Command": | ||
| 3064 | command = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 3065 | break; | ||
| 3066 | case "OnInstall": | ||
| 3067 | install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3068 | break; | ||
| 3069 | case "OnReinstall": | ||
| 3070 | reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3071 | break; | ||
| 3072 | case "OnUninstall": | ||
| 3073 | uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3074 | break; | ||
| 3075 | case "RebootMessage": | ||
| 3076 | rebootMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 3077 | break; | ||
| 3078 | case "ResetPeriod": | ||
| 3079 | resetPeriod = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 3080 | break; | ||
| 3081 | case "ServiceName": | ||
| 3082 | if (!String.IsNullOrEmpty(serviceName)) | ||
| 3083 | { | ||
| 3084 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); | ||
| 3085 | } | ||
| 3086 | |||
| 3087 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3088 | break; | ||
| 3089 | default: | ||
| 3090 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3091 | break; | ||
| 3092 | } | ||
| 3093 | } | ||
| 3094 | else | ||
| 3095 | { | ||
| 3096 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3097 | } | ||
| 3098 | } | ||
| 3099 | |||
| 3100 | // Get the ServiceConfigFailureActions actions. | ||
| 3101 | foreach (var child in node.Elements()) | ||
| 3102 | { | ||
| 3103 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 3104 | { | ||
| 3105 | switch (child.Name.LocalName) | ||
| 3106 | { | ||
| 3107 | case "Failure": | ||
| 3108 | string action = null; | ||
| 3109 | string delay = null; | ||
| 3110 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 3111 | |||
| 3112 | foreach (var childAttrib in child.Attributes()) | ||
| 3113 | { | ||
| 3114 | if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace) | ||
| 3115 | { | ||
| 3116 | switch (childAttrib.Name.LocalName) | ||
| 3117 | { | ||
| 3118 | case "Action": | ||
| 3119 | action = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
| 3120 | switch (action) | ||
| 3121 | { | ||
| 3122 | case "none": | ||
| 3123 | action = "0"; | ||
| 3124 | break; | ||
| 3125 | case "restartComputer": | ||
| 3126 | action = "2"; | ||
| 3127 | break; | ||
| 3128 | case "restartService": | ||
| 3129 | action = "1"; | ||
| 3130 | break; | ||
| 3131 | case "runCommand": | ||
| 3132 | action = "3"; | ||
| 3133 | break; | ||
| 3134 | default: | ||
| 3135 | // allow everything else to pass through that are hopefully "formatted" Properties. | ||
| 3136 | break; | ||
| 3137 | } | ||
| 3138 | break; | ||
| 3139 | case "Delay": | ||
| 3140 | delay = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); | ||
| 3141 | break; | ||
| 3142 | default: | ||
| 3143 | this.Core.UnexpectedAttribute(child, childAttrib); | ||
| 3144 | break; | ||
| 3145 | } | ||
| 3146 | } | ||
| 3147 | } | ||
| 3148 | |||
| 3149 | if (String.IsNullOrEmpty(action)) | ||
| 3150 | { | ||
| 3151 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action")); | ||
| 3152 | } | ||
| 3153 | |||
| 3154 | if (String.IsNullOrEmpty(delay)) | ||
| 3155 | { | ||
| 3156 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay")); | ||
| 3157 | } | ||
| 3158 | |||
| 3159 | if (!String.IsNullOrEmpty(actions)) | ||
| 3160 | { | ||
| 3161 | actions = String.Concat(actions, "[~]"); | ||
| 3162 | } | ||
| 3163 | actions = String.Concat(actions, action); | ||
| 3164 | |||
| 3165 | if (!String.IsNullOrEmpty(actionsDelays)) | ||
| 3166 | { | ||
| 3167 | actionsDelays = String.Concat(actionsDelays, "[~]"); | ||
| 3168 | } | ||
| 3169 | actionsDelays = String.Concat(actionsDelays, delay); | ||
| 3170 | break; | ||
| 3171 | default: | ||
| 3172 | this.Core.UnexpectedElement(node, child); | ||
| 3173 | break; | ||
| 3174 | } | ||
| 3175 | } | ||
| 3176 | else | ||
| 3177 | { | ||
| 3178 | this.Core.ParseExtensionElement(node, child); | ||
| 3179 | } | ||
| 3180 | } | ||
| 3181 | |||
| 3182 | if (String.IsNullOrEmpty(name)) | ||
| 3183 | { | ||
| 3184 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); | ||
| 3185 | } | ||
| 3186 | else if (null == id) | ||
| 3187 | { | ||
| 3188 | id = this.Core.CreateIdentifierFromFilename(name); | ||
| 3189 | } | ||
| 3190 | |||
| 3191 | if (!install && !reinstall && !uninstall) | ||
| 3192 | { | ||
| 3193 | this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); | ||
| 3194 | } | ||
| 3195 | |||
| 3196 | if (!this.Core.EncounteredError) | ||
| 3197 | { | ||
| 3198 | this.Core.AddSymbol(new MsiServiceConfigFailureActionsSymbol(sourceLineNumbers, id) | ||
| 3199 | { | ||
| 3200 | Name = name, | ||
| 3201 | OnInstall = install, | ||
| 3202 | OnReinstall = reinstall, | ||
| 3203 | OnUninstall = uninstall, | ||
| 3204 | ResetPeriod = resetPeriod, | ||
| 3205 | RebootMessage = rebootMessage, | ||
| 3206 | Command = command, | ||
| 3207 | Actions = actions, | ||
| 3208 | DelayActions = actionsDelays, | ||
| 3209 | ComponentRef = componentId, | ||
| 3210 | }); | ||
| 3211 | } | ||
| 3212 | } | ||
| 3213 | |||
| 3214 | /// <summary> | ||
| 3215 | /// Parses a service control element. | ||
| 3216 | /// </summary> | ||
| 3217 | /// <param name="node">Element to parse.</param> | ||
| 3218 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 3219 | private void ParseServiceControlElement(XElement node, string componentId) | ||
| 3220 | { | ||
| 3221 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3222 | string arguments = null; | ||
| 3223 | Identifier id = null; | ||
| 3224 | string name = null; | ||
| 3225 | var installRemove = false; | ||
| 3226 | var uninstallRemove = false; | ||
| 3227 | var installStart = false; | ||
| 3228 | var uninstallStart = false; | ||
| 3229 | var installStop = false; | ||
| 3230 | var uninstallStop = false; | ||
| 3231 | bool? wait = null; | ||
| 3232 | |||
| 3233 | foreach (var attrib in node.Attributes()) | ||
| 3234 | { | ||
| 3235 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3236 | { | ||
| 3237 | switch (attrib.Name.LocalName) | ||
| 3238 | { | ||
| 3239 | case "Id": | ||
| 3240 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 3241 | break; | ||
| 3242 | case "Name": | ||
| 3243 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3244 | break; | ||
| 3245 | case "Remove": | ||
| 3246 | var removeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3247 | switch (removeValue) | ||
| 3248 | { | ||
| 3249 | case "install": | ||
| 3250 | installRemove = true; | ||
| 3251 | break; | ||
| 3252 | case "uninstall": | ||
| 3253 | uninstallRemove = true; | ||
| 3254 | break; | ||
| 3255 | case "both": | ||
| 3256 | installRemove = true; | ||
| 3257 | uninstallRemove = true; | ||
| 3258 | break; | ||
| 3259 | case "": | ||
| 3260 | break; | ||
| 3261 | } | ||
| 3262 | break; | ||
| 3263 | case "Start": | ||
| 3264 | var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3265 | switch (startValue) | ||
| 3266 | { | ||
| 3267 | case "install": | ||
| 3268 | installStart = true; | ||
| 3269 | break; | ||
| 3270 | case "uninstall": | ||
| 3271 | uninstallStart = true; | ||
| 3272 | break; | ||
| 3273 | case "both": | ||
| 3274 | installStart = true; | ||
| 3275 | uninstallStart = true; | ||
| 3276 | break; | ||
| 3277 | case "": | ||
| 3278 | break; | ||
| 3279 | } | ||
| 3280 | break; | ||
| 3281 | case "Stop": | ||
| 3282 | var stopValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3283 | switch (stopValue) | ||
| 3284 | { | ||
| 3285 | case "install": | ||
| 3286 | installStop = true; | ||
| 3287 | break; | ||
| 3288 | case "uninstall": | ||
| 3289 | uninstallStop = true; | ||
| 3290 | break; | ||
| 3291 | case "both": | ||
| 3292 | installStop = true; | ||
| 3293 | uninstallStop = true; | ||
| 3294 | break; | ||
| 3295 | case "": | ||
| 3296 | break; | ||
| 3297 | } | ||
| 3298 | break; | ||
| 3299 | case "Wait": | ||
| 3300 | wait = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3301 | break; | ||
| 3302 | default: | ||
| 3303 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3304 | break; | ||
| 3305 | } | ||
| 3306 | } | ||
| 3307 | else | ||
| 3308 | { | ||
| 3309 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3310 | } | ||
| 3311 | } | ||
| 3312 | |||
| 3313 | if (null == id) | ||
| 3314 | { | ||
| 3315 | id = this.Core.CreateIdentifierFromFilename(name); | ||
| 3316 | } | ||
| 3317 | |||
| 3318 | if (null == name) | ||
| 3319 | { | ||
| 3320 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 3321 | } | ||
| 3322 | |||
| 3323 | // get the ServiceControl arguments | ||
| 3324 | foreach (var child in node.Elements()) | ||
| 3325 | { | ||
| 3326 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 3327 | { | ||
| 3328 | switch (child.Name.LocalName) | ||
| 3329 | { | ||
| 3330 | case "ServiceArgument": | ||
| 3331 | arguments = this.ParseServiceArgument(child, arguments); | ||
| 3332 | break; | ||
| 3333 | default: | ||
| 3334 | this.Core.UnexpectedElement(node, child); | ||
| 3335 | break; | ||
| 3336 | } | ||
| 3337 | } | ||
| 3338 | else | ||
| 3339 | { | ||
| 3340 | this.Core.ParseExtensionElement(node, child); | ||
| 3341 | } | ||
| 3342 | } | ||
| 3343 | |||
| 3344 | if (!this.Core.EncounteredError) | ||
| 3345 | { | ||
| 3346 | this.Core.AddSymbol(new ServiceControlSymbol(sourceLineNumbers, id) | ||
| 3347 | { | ||
| 3348 | Name = name, | ||
| 3349 | InstallRemove = installRemove, | ||
| 3350 | UninstallRemove = uninstallRemove, | ||
| 3351 | InstallStart = installStart, | ||
| 3352 | UninstallStart = uninstallStart, | ||
| 3353 | InstallStop = installStop, | ||
| 3354 | UninstallStop = uninstallStop, | ||
| 3355 | Arguments = arguments, | ||
| 3356 | Wait = wait, | ||
| 3357 | ComponentRef = componentId | ||
| 3358 | }); | ||
| 3359 | } | ||
| 3360 | } | ||
| 3361 | |||
| 3362 | private string ParseServiceArgument(XElement node, string arguments) | ||
| 3363 | { | ||
| 3364 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3365 | string argument = null; | ||
| 3366 | |||
| 3367 | foreach (var attrib in node.Attributes()) | ||
| 3368 | { | ||
| 3369 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3370 | { | ||
| 3371 | switch (attrib.Name.LocalName) | ||
| 3372 | { | ||
| 3373 | case "Value": | ||
| 3374 | argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3375 | break; | ||
| 3376 | default: | ||
| 3377 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3378 | break; | ||
| 3379 | } | ||
| 3380 | } | ||
| 3381 | } | ||
| 3382 | |||
| 3383 | if (argument == null) | ||
| 3384 | { | ||
| 3385 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 3386 | } | ||
| 3387 | |||
| 3388 | this.Core.ParseForExtensionElements(node); | ||
| 3389 | |||
| 3390 | return (arguments == null) ? argument : String.Concat(arguments, "[~]", argument); | ||
| 3391 | } | ||
| 3392 | |||
| 3393 | /// <summary> | ||
| 3394 | /// Parses a service dependency element. | ||
| 3395 | /// </summary> | ||
| 3396 | /// <param name="node">Element to parse.</param> | ||
| 3397 | /// <returns>Parsed sevice dependency name.</returns> | ||
| 3398 | private string ParseServiceDependencyElement(XElement node) | ||
| 3399 | { | ||
| 3400 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3401 | string dependency = null; | ||
| 3402 | var group = false; | ||
| 3403 | |||
| 3404 | foreach (var attrib in node.Attributes()) | ||
| 3405 | { | ||
| 3406 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3407 | { | ||
| 3408 | switch (attrib.Name.LocalName) | ||
| 3409 | { | ||
| 3410 | case "Id": | ||
| 3411 | dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3412 | break; | ||
| 3413 | case "Group": | ||
| 3414 | group = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3415 | break; | ||
| 3416 | default: | ||
| 3417 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3418 | break; | ||
| 3419 | } | ||
| 3420 | } | ||
| 3421 | else | ||
| 3422 | { | ||
| 3423 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3424 | } | ||
| 3425 | } | ||
| 3426 | |||
| 3427 | if (null == dependency) | ||
| 3428 | { | ||
| 3429 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3430 | } | ||
| 3431 | |||
| 3432 | this.Core.ParseForExtensionElements(node); | ||
| 3433 | |||
| 3434 | return group ? String.Concat("+", dependency) : dependency; | ||
| 3435 | } | ||
| 3436 | |||
| 3437 | /// <summary> | ||
| 3438 | /// Parses a service install element. | ||
| 3439 | /// </summary> | ||
| 3440 | /// <param name="node">Element to parse.</param> | ||
| 3441 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 3442 | /// <param name="win64Component"></param> | ||
| 3443 | private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component) | ||
| 3444 | { | ||
| 3445 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3446 | Identifier id = null; | ||
| 3447 | string account = null; | ||
| 3448 | string arguments = null; | ||
| 3449 | string dependencies = null; | ||
| 3450 | string description = null; | ||
| 3451 | string displayName = null; | ||
| 3452 | var eraseDescription = false; | ||
| 3453 | string loadOrderGroup = null; | ||
| 3454 | string name = null; | ||
| 3455 | string password = null; | ||
| 3456 | |||
| 3457 | var serviceType = ServiceType.OwnProcess; | ||
| 3458 | var startType = ServiceStartType.Demand; | ||
| 3459 | var errorControl = ServiceErrorControl.Normal; | ||
| 3460 | var interactive = false; | ||
| 3461 | var vital = false; | ||
| 3462 | |||
| 3463 | foreach (var attrib in node.Attributes()) | ||
| 3464 | { | ||
| 3465 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3466 | { | ||
| 3467 | switch (attrib.Name.LocalName) | ||
| 3468 | { | ||
| 3469 | case "Id": | ||
| 3470 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 3471 | break; | ||
| 3472 | case "Account": | ||
| 3473 | account = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3474 | break; | ||
| 3475 | case "Arguments": | ||
| 3476 | arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3477 | break; | ||
| 3478 | case "Description": | ||
| 3479 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3480 | break; | ||
| 3481 | case "DisplayName": | ||
| 3482 | displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3483 | break; | ||
| 3484 | case "EraseDescription": | ||
| 3485 | eraseDescription = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3486 | break; | ||
| 3487 | case "ErrorControl": | ||
| 3488 | var errorControlValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3489 | switch (errorControlValue) | ||
| 3490 | { | ||
| 3491 | case "ignore": | ||
| 3492 | errorControl = ServiceErrorControl.Ignore; | ||
| 3493 | break; | ||
| 3494 | case "normal": | ||
| 3495 | errorControl = ServiceErrorControl.Normal; | ||
| 3496 | break; | ||
| 3497 | case "critical": | ||
| 3498 | errorControl = ServiceErrorControl.Critical; | ||
| 3499 | break; | ||
| 3500 | case "": // error case handled by GetAttributeValue() | ||
| 3501 | break; | ||
| 3502 | default: | ||
| 3503 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical")); | ||
| 3504 | break; | ||
| 3505 | } | ||
| 3506 | break; | ||
| 3507 | case "Interactive": | ||
| 3508 | interactive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3509 | break; | ||
| 3510 | case "LoadOrderGroup": | ||
| 3511 | loadOrderGroup = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3512 | break; | ||
| 3513 | case "Name": | ||
| 3514 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3515 | break; | ||
| 3516 | case "Password": | ||
| 3517 | password = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3518 | break; | ||
| 3519 | case "Start": | ||
| 3520 | var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3521 | switch (startValue) | ||
| 3522 | { | ||
| 3523 | case "auto": | ||
| 3524 | startType = ServiceStartType.Auto; | ||
| 3525 | break; | ||
| 3526 | case "demand": | ||
| 3527 | startType = ServiceStartType.Demand; | ||
| 3528 | break; | ||
| 3529 | case "disabled": | ||
| 3530 | startType = ServiceStartType.Disabled; | ||
| 3531 | break; | ||
| 3532 | case "boot": | ||
| 3533 | case "system": | ||
| 3534 | this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue)); | ||
| 3535 | break; | ||
| 3536 | case "": | ||
| 3537 | break; | ||
| 3538 | default: | ||
| 3539 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled")); | ||
| 3540 | break; | ||
| 3541 | } | ||
| 3542 | break; | ||
| 3543 | case "Type": | ||
| 3544 | var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3545 | switch (typeValue) | ||
| 3546 | { | ||
| 3547 | case "ownProcess": | ||
| 3548 | serviceType = ServiceType.OwnProcess; | ||
| 3549 | break; | ||
| 3550 | case "shareProcess": | ||
| 3551 | serviceType = ServiceType.ShareProcess; | ||
| 3552 | break; | ||
| 3553 | case "kernelDriver": | ||
| 3554 | case "systemDriver": | ||
| 3555 | this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue)); | ||
| 3556 | break; | ||
| 3557 | case "": | ||
| 3558 | break; | ||
| 3559 | default: | ||
| 3560 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess")); | ||
| 3561 | break; | ||
| 3562 | } | ||
| 3563 | break; | ||
| 3564 | case "Vital": | ||
| 3565 | vital = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 3566 | break; | ||
| 3567 | default: | ||
| 3568 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3569 | break; | ||
| 3570 | } | ||
| 3571 | } | ||
| 3572 | else | ||
| 3573 | { | ||
| 3574 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3575 | } | ||
| 3576 | } | ||
| 3577 | |||
| 3578 | if (String.IsNullOrEmpty(name)) | ||
| 3579 | { | ||
| 3580 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 3581 | } | ||
| 3582 | else if (null == id) | ||
| 3583 | { | ||
| 3584 | id = this.Core.CreateIdentifierFromFilename(name); | ||
| 3585 | } | ||
| 3586 | |||
| 3587 | if (0 == startType) | ||
| 3588 | { | ||
| 3589 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start")); | ||
| 3590 | } | ||
| 3591 | |||
| 3592 | if (eraseDescription) | ||
| 3593 | { | ||
| 3594 | description = "[~]"; | ||
| 3595 | } | ||
| 3596 | |||
| 3597 | // get the ServiceInstall dependencies and config | ||
| 3598 | foreach (var child in node.Elements()) | ||
| 3599 | { | ||
| 3600 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 3601 | { | ||
| 3602 | switch (child.Name.LocalName) | ||
| 3603 | { | ||
| 3604 | case "PermissionEx": | ||
| 3605 | this.ParsePermissionExElement(child, id.Id, "ServiceInstall"); | ||
| 3606 | break; | ||
| 3607 | case "ServiceConfig": | ||
| 3608 | this.ParseServiceConfigElement(child, componentId, name); | ||
| 3609 | break; | ||
| 3610 | case "ServiceConfigFailureActions": | ||
| 3611 | this.ParseServiceConfigFailureActionsElement(child, componentId, name); | ||
| 3612 | break; | ||
| 3613 | case "ServiceDependency": | ||
| 3614 | dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]"); | ||
| 3615 | break; | ||
| 3616 | default: | ||
| 3617 | this.Core.UnexpectedElement(node, child); | ||
| 3618 | break; | ||
| 3619 | } | ||
| 3620 | } | ||
| 3621 | else | ||
| 3622 | { | ||
| 3623 | var context = new Dictionary<string, string>() { { "ServiceInstallId", id?.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } }; | ||
| 3624 | this.Core.ParseExtensionElement(node, child, context); | ||
| 3625 | } | ||
| 3626 | } | ||
| 3627 | |||
| 3628 | if (null != dependencies) | ||
| 3629 | { | ||
| 3630 | dependencies = String.Concat(dependencies, "[~]"); | ||
| 3631 | } | ||
| 3632 | |||
| 3633 | if (!this.Core.EncounteredError) | ||
| 3634 | { | ||
| 3635 | this.Core.AddSymbol(new ServiceInstallSymbol(sourceLineNumbers, id) | ||
| 3636 | { | ||
| 3637 | Name = name, | ||
| 3638 | DisplayName = displayName, | ||
| 3639 | ServiceType = serviceType, | ||
| 3640 | StartType = startType, | ||
| 3641 | ErrorControl = errorControl, | ||
| 3642 | LoadOrderGroup = loadOrderGroup, | ||
| 3643 | Dependencies = dependencies, | ||
| 3644 | StartName = account, | ||
| 3645 | Password = password, | ||
| 3646 | Arguments = arguments, | ||
| 3647 | ComponentRef = componentId, | ||
| 3648 | Description = description, | ||
| 3649 | Interactive = interactive, | ||
| 3650 | Vital = vital | ||
| 3651 | }); | ||
| 3652 | } | ||
| 3653 | } | ||
| 3654 | |||
| 3655 | /// <summary> | ||
| 3656 | /// Parses a SetDirectory element. | ||
| 3657 | /// </summary> | ||
| 3658 | /// <param name="node">Element to parse.</param> | ||
| 3659 | private void ParseSetDirectoryElement(XElement node) | ||
| 3660 | { | ||
| 3661 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3662 | string actionName = null; | ||
| 3663 | string id = null; | ||
| 3664 | string condition = null; | ||
| 3665 | var executionType = CustomActionExecutionType.Immediate; | ||
| 3666 | var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" | ||
| 3667 | string value = null; | ||
| 3668 | |||
| 3669 | foreach (var attrib in node.Attributes()) | ||
| 3670 | { | ||
| 3671 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3672 | { | ||
| 3673 | switch (attrib.Name.LocalName) | ||
| 3674 | { | ||
| 3675 | case "Action": | ||
| 3676 | actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3677 | break; | ||
| 3678 | case "Condition": | ||
| 3679 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3680 | break; | ||
| 3681 | case "Id": | ||
| 3682 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3683 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); | ||
| 3684 | break; | ||
| 3685 | case "Sequence": | ||
| 3686 | var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3687 | switch (sequenceValue) | ||
| 3688 | { | ||
| 3689 | case "execute": | ||
| 3690 | sequences = new[] { SequenceTable.InstallExecuteSequence }; | ||
| 3691 | break; | ||
| 3692 | case "first": | ||
| 3693 | executionType = CustomActionExecutionType.FirstSequence; | ||
| 3694 | break; | ||
| 3695 | case "ui": | ||
| 3696 | sequences = new[] { SequenceTable.InstallUISequence }; | ||
| 3697 | break; | ||
| 3698 | case "both": | ||
| 3699 | break; | ||
| 3700 | case "": | ||
| 3701 | break; | ||
| 3702 | default: | ||
| 3703 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); | ||
| 3704 | break; | ||
| 3705 | } | ||
| 3706 | break; | ||
| 3707 | case "Value": | ||
| 3708 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3709 | break; | ||
| 3710 | default: | ||
| 3711 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3712 | break; | ||
| 3713 | } | ||
| 3714 | } | ||
| 3715 | else | ||
| 3716 | { | ||
| 3717 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3718 | } | ||
| 3719 | } | ||
| 3720 | |||
| 3721 | if (null == id) | ||
| 3722 | { | ||
| 3723 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3724 | } | ||
| 3725 | else if (String.IsNullOrEmpty(actionName)) | ||
| 3726 | { | ||
| 3727 | actionName = String.Concat("Set", id); | ||
| 3728 | } | ||
| 3729 | |||
| 3730 | if (null == value) | ||
| 3731 | { | ||
| 3732 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 3733 | } | ||
| 3734 | |||
| 3735 | this.Core.ParseForExtensionElements(node); | ||
| 3736 | |||
| 3737 | if (!this.Core.EncounteredError) | ||
| 3738 | { | ||
| 3739 | this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) | ||
| 3740 | { | ||
| 3741 | ExecutionType = executionType, | ||
| 3742 | SourceType = CustomActionSourceType.Directory, | ||
| 3743 | TargetType = CustomActionTargetType.TextData, | ||
| 3744 | Source = id, | ||
| 3745 | Target = value | ||
| 3746 | }); | ||
| 3747 | |||
| 3748 | foreach (var sequence in sequences) | ||
| 3749 | { | ||
| 3750 | this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, afterAction: "CostInitialize"); | ||
| 3751 | } | ||
| 3752 | } | ||
| 3753 | } | ||
| 3754 | |||
| 3755 | /// <summary> | ||
| 3756 | /// Parses a SetProperty element. | ||
| 3757 | /// </summary> | ||
| 3758 | /// <param name="node">Element to parse.</param> | ||
| 3759 | private void ParseSetPropertyElement(XElement node) | ||
| 3760 | { | ||
| 3761 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3762 | string actionName = null; | ||
| 3763 | string id = null; | ||
| 3764 | string condition = null; | ||
| 3765 | string afterAction = null; | ||
| 3766 | string beforeAction = null; | ||
| 3767 | var executionType = CustomActionExecutionType.Immediate; | ||
| 3768 | var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" | ||
| 3769 | string value = null; | ||
| 3770 | |||
| 3771 | foreach (var attrib in node.Attributes()) | ||
| 3772 | { | ||
| 3773 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3774 | { | ||
| 3775 | switch (attrib.Name.LocalName) | ||
| 3776 | { | ||
| 3777 | case "Action": | ||
| 3778 | actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3779 | break; | ||
| 3780 | case "Id": | ||
| 3781 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3782 | break; | ||
| 3783 | case "Condition": | ||
| 3784 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3785 | break; | ||
| 3786 | case "After": | ||
| 3787 | afterAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3788 | break; | ||
| 3789 | case "Before": | ||
| 3790 | beforeAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3791 | break; | ||
| 3792 | case "Sequence": | ||
| 3793 | var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3794 | switch (sequenceValue) | ||
| 3795 | { | ||
| 3796 | case "execute": | ||
| 3797 | sequences = new[] { SequenceTable.InstallExecuteSequence }; | ||
| 3798 | break; | ||
| 3799 | case "first": | ||
| 3800 | executionType = CustomActionExecutionType.FirstSequence; | ||
| 3801 | break; | ||
| 3802 | case "ui": | ||
| 3803 | sequences = new[] { SequenceTable.InstallUISequence }; | ||
| 3804 | break; | ||
| 3805 | case "both": | ||
| 3806 | break; | ||
| 3807 | case "": | ||
| 3808 | break; | ||
| 3809 | default: | ||
| 3810 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); | ||
| 3811 | break; | ||
| 3812 | } | ||
| 3813 | break; | ||
| 3814 | case "Value": | ||
| 3815 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 3816 | break; | ||
| 3817 | default: | ||
| 3818 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3819 | break; | ||
| 3820 | } | ||
| 3821 | } | ||
| 3822 | else | ||
| 3823 | { | ||
| 3824 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3825 | } | ||
| 3826 | } | ||
| 3827 | |||
| 3828 | if (null == id) | ||
| 3829 | { | ||
| 3830 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3831 | } | ||
| 3832 | else if (String.IsNullOrEmpty(actionName)) | ||
| 3833 | { | ||
| 3834 | actionName = String.Concat("Set", id); | ||
| 3835 | } | ||
| 3836 | |||
| 3837 | if (null == value) | ||
| 3838 | { | ||
| 3839 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 3840 | } | ||
| 3841 | |||
| 3842 | if (null != beforeAction && null != afterAction) | ||
| 3843 | { | ||
| 3844 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before")); | ||
| 3845 | } | ||
| 3846 | else if (null == beforeAction && null == afterAction) | ||
| 3847 | { | ||
| 3848 | this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id")); | ||
| 3849 | } | ||
| 3850 | |||
| 3851 | this.Core.ParseForExtensionElements(node); | ||
| 3852 | |||
| 3853 | // add the row and any references needed | ||
| 3854 | if (!this.Core.EncounteredError) | ||
| 3855 | { | ||
| 3856 | // action that is scheduled to occur before/after itself | ||
| 3857 | if (beforeAction == actionName) | ||
| 3858 | { | ||
| 3859 | this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction)); | ||
| 3860 | } | ||
| 3861 | else if (afterAction == actionName) | ||
| 3862 | { | ||
| 3863 | this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction)); | ||
| 3864 | } | ||
| 3865 | |||
| 3866 | this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) | ||
| 3867 | { | ||
| 3868 | ExecutionType = executionType, | ||
| 3869 | SourceType = CustomActionSourceType.Property, | ||
| 3870 | TargetType = CustomActionTargetType.TextData, | ||
| 3871 | Source = id, | ||
| 3872 | Target = value, | ||
| 3873 | }); | ||
| 3874 | |||
| 3875 | foreach (var sequence in sequences) | ||
| 3876 | { | ||
| 3877 | this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, beforeAction, afterAction); | ||
| 3878 | } | ||
| 3879 | } | ||
| 3880 | } | ||
| 3881 | |||
| 3882 | /// <summary> | ||
| 3883 | /// Parses a SFP catalog element. | ||
| 3884 | /// </summary> | ||
| 3885 | /// <param name="node">Element to parse.</param> | ||
| 3886 | /// <param name="parentSFPCatalog">Parent SFPCatalog.</param> | ||
| 3887 | private void ParseSFPFileElement(XElement node, string parentSFPCatalog) | ||
| 3888 | { | ||
| 3889 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3890 | string id = null; | ||
| 3891 | |||
| 3892 | foreach (var attrib in node.Attributes()) | ||
| 3893 | { | ||
| 3894 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3895 | { | ||
| 3896 | switch (attrib.Name.LocalName) | ||
| 3897 | { | ||
| 3898 | case "Id": | ||
| 3899 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 3900 | break; | ||
| 3901 | default: | ||
| 3902 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3903 | break; | ||
| 3904 | } | ||
| 3905 | } | ||
| 3906 | else | ||
| 3907 | { | ||
| 3908 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3909 | } | ||
| 3910 | } | ||
| 3911 | |||
| 3912 | if (null == id) | ||
| 3913 | { | ||
| 3914 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 3915 | } | ||
| 3916 | |||
| 3917 | this.Core.ParseForExtensionElements(node); | ||
| 3918 | |||
| 3919 | if (!this.Core.EncounteredError) | ||
| 3920 | { | ||
| 3921 | this.Core.AddSymbol(new FileSFPCatalogSymbol(sourceLineNumbers) | ||
| 3922 | { | ||
| 3923 | FileRef = id, | ||
| 3924 | SFPCatalogRef = parentSFPCatalog | ||
| 3925 | }); | ||
| 3926 | } | ||
| 3927 | } | ||
| 3928 | |||
| 3929 | /// <summary> | ||
| 3930 | /// Parses a SFP catalog element. | ||
| 3931 | /// </summary> | ||
| 3932 | /// <param name="node">Element to parse.</param> | ||
| 3933 | /// <param name="parentSFPCatalog">Parent SFPCatalog.</param> | ||
| 3934 | private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog) | ||
| 3935 | { | ||
| 3936 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 3937 | string parentName = null; | ||
| 3938 | string dependency = null; | ||
| 3939 | string name = null; | ||
| 3940 | string sourceFile = null; | ||
| 3941 | |||
| 3942 | foreach (var attrib in node.Attributes()) | ||
| 3943 | { | ||
| 3944 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 3945 | { | ||
| 3946 | switch (attrib.Name.LocalName) | ||
| 3947 | { | ||
| 3948 | case "Dependency": | ||
| 3949 | dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3950 | break; | ||
| 3951 | case "Name": | ||
| 3952 | name = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
| 3953 | parentSFPCatalog = name; | ||
| 3954 | break; | ||
| 3955 | case "SourceFile": | ||
| 3956 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 3957 | break; | ||
| 3958 | default: | ||
| 3959 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 3960 | break; | ||
| 3961 | } | ||
| 3962 | } | ||
| 3963 | else | ||
| 3964 | { | ||
| 3965 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 3966 | } | ||
| 3967 | } | ||
| 3968 | |||
| 3969 | if (null == name) | ||
| 3970 | { | ||
| 3971 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 3972 | } | ||
| 3973 | |||
| 3974 | if (null == sourceFile) | ||
| 3975 | { | ||
| 3976 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
| 3977 | } | ||
| 3978 | |||
| 3979 | foreach (var child in node.Elements()) | ||
| 3980 | { | ||
| 3981 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 3982 | { | ||
| 3983 | switch (child.Name.LocalName) | ||
| 3984 | { | ||
| 3985 | case "SFPCatalog": | ||
| 3986 | this.ParseSFPCatalogElement(child, ref parentName); | ||
| 3987 | if (null != dependency && parentName == dependency) | ||
| 3988 | { | ||
| 3989 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); | ||
| 3990 | } | ||
| 3991 | dependency = parentName; | ||
| 3992 | break; | ||
| 3993 | case "SFPFile": | ||
| 3994 | this.ParseSFPFileElement(child, name); | ||
| 3995 | break; | ||
| 3996 | default: | ||
| 3997 | this.Core.UnexpectedElement(node, child); | ||
| 3998 | break; | ||
| 3999 | } | ||
| 4000 | } | ||
| 4001 | else | ||
| 4002 | { | ||
| 4003 | this.Core.ParseExtensionElement(node, child); | ||
| 4004 | } | ||
| 4005 | } | ||
| 4006 | |||
| 4007 | if (null == dependency) | ||
| 4008 | { | ||
| 4009 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); | ||
| 4010 | } | ||
| 4011 | |||
| 4012 | if (!this.Core.EncounteredError) | ||
| 4013 | { | ||
| 4014 | this.Core.AddSymbol(new SFPCatalogSymbol(sourceLineNumbers) | ||
| 4015 | { | ||
| 4016 | SFPCatalog = name, | ||
| 4017 | Catalog = sourceFile, | ||
| 4018 | Dependency = dependency | ||
| 4019 | }); | ||
| 4020 | } | ||
| 4021 | } | ||
| 4022 | |||
| 4023 | /// <summary> | ||
| 4024 | /// Parses a shortcut element. | ||
| 4025 | /// </summary> | ||
| 4026 | /// <param name="node">Element to parse.</param> | ||
| 4027 | /// <param name="componentId">Identifer for parent component.</param> | ||
| 4028 | /// <param name="parentElementLocalName">Local name of parent element.</param> | ||
| 4029 | /// <param name="defaultTarget">Default identifier of parent (which is usually the target).</param> | ||
| 4030 | /// <param name="parentKeyPath">Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements).</param> | ||
| 4031 | private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath) | ||
| 4032 | { | ||
| 4033 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4034 | Identifier id = null; | ||
| 4035 | var advertise = false; | ||
| 4036 | string arguments = null; | ||
| 4037 | string description = null; | ||
| 4038 | string descriptionResourceDll = null; | ||
| 4039 | int? descriptionResourceId = null; | ||
| 4040 | string directoryId = null; | ||
| 4041 | string subdirectory = null; | ||
| 4042 | string displayResourceDll = null; | ||
| 4043 | int? displayResourceId = null; | ||
| 4044 | int? hotkey = null; | ||
| 4045 | string icon = null; | ||
| 4046 | int? iconIndex = null; | ||
| 4047 | string name = null; | ||
| 4048 | string shortName = null; | ||
| 4049 | ShortcutShowType? show = null; | ||
| 4050 | string target = null; | ||
| 4051 | string workingDirectoryId = null; | ||
| 4052 | string workingSubdirectory = null; | ||
| 4053 | |||
| 4054 | foreach (var attrib in node.Attributes()) | ||
| 4055 | { | ||
| 4056 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4057 | { | ||
| 4058 | switch (attrib.Name.LocalName) | ||
| 4059 | { | ||
| 4060 | case "Id": | ||
| 4061 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 4062 | break; | ||
| 4063 | case "Advertise": | ||
| 4064 | advertise = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4065 | break; | ||
| 4066 | case "Arguments": | ||
| 4067 | arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4068 | break; | ||
| 4069 | case "Description": | ||
| 4070 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4071 | break; | ||
| 4072 | case "DescriptionResourceDll": | ||
| 4073 | descriptionResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4074 | break; | ||
| 4075 | case "DescriptionResourceId": | ||
| 4076 | descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 4077 | break; | ||
| 4078 | case "Directory": | ||
| 4079 | directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 4080 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); | ||
| 4081 | break; | ||
| 4082 | case "Subdirectory": | ||
| 4083 | subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 4084 | break; | ||
| 4085 | case "DisplayResourceDll": | ||
| 4086 | displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4087 | break; | ||
| 4088 | case "DisplayResourceId": | ||
| 4089 | displayResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 4090 | break; | ||
| 4091 | case "Hotkey": | ||
| 4092 | hotkey = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 4093 | break; | ||
| 4094 | case "Icon": | ||
| 4095 | icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 4096 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); | ||
| 4097 | break; | ||
| 4098 | case "IconIndex": | ||
| 4099 | iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); | ||
| 4100 | break; | ||
| 4101 | case "Name": | ||
| 4102 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 4103 | break; | ||
| 4104 | case "ShortName": | ||
| 4105 | shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); | ||
| 4106 | break; | ||
| 4107 | case "Show": | ||
| 4108 | var showValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4109 | switch (showValue) | ||
| 4110 | { | ||
| 4111 | case "normal": | ||
| 4112 | show = ShortcutShowType.Normal; | ||
| 4113 | break; | ||
| 4114 | case "maximized": | ||
| 4115 | show = ShortcutShowType.Maximized; | ||
| 4116 | break; | ||
| 4117 | case "minimized": | ||
| 4118 | show = ShortcutShowType.Minimized; | ||
| 4119 | break; | ||
| 4120 | case "": | ||
| 4121 | break; | ||
| 4122 | default: | ||
| 4123 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); | ||
| 4124 | break; | ||
| 4125 | } | ||
| 4126 | break; | ||
| 4127 | case "Target": | ||
| 4128 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4129 | break; | ||
| 4130 | case "WorkingDirectory": | ||
| 4131 | workingDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 4132 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, workingDirectoryId); | ||
| 4133 | break; | ||
| 4134 | case "WorkingSubdirectory": | ||
| 4135 | workingSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 4136 | break; | ||
| 4137 | default: | ||
| 4138 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4139 | break; | ||
| 4140 | } | ||
| 4141 | } | ||
| 4142 | else | ||
| 4143 | { | ||
| 4144 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4145 | } | ||
| 4146 | } | ||
| 4147 | |||
| 4148 | if (advertise && null != target) | ||
| 4149 | { | ||
| 4150 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); | ||
| 4151 | } | ||
| 4152 | |||
| 4153 | if (null == directoryId) | ||
| 4154 | { | ||
| 4155 | if ("Component" == parentElementLocalName) | ||
| 4156 | { | ||
| 4157 | directoryId = defaultTarget; | ||
| 4158 | } | ||
| 4159 | else | ||
| 4160 | { | ||
| 4161 | this.Core.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component")); | ||
| 4162 | } | ||
| 4163 | } | ||
| 4164 | |||
| 4165 | directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); | ||
| 4166 | |||
| 4167 | if (null != descriptionResourceDll) | ||
| 4168 | { | ||
| 4169 | if (!descriptionResourceId.HasValue) | ||
| 4170 | { | ||
| 4171 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId")); | ||
| 4172 | } | ||
| 4173 | } | ||
| 4174 | else | ||
| 4175 | { | ||
| 4176 | if (descriptionResourceId.HasValue) | ||
| 4177 | { | ||
| 4178 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll")); | ||
| 4179 | } | ||
| 4180 | } | ||
| 4181 | |||
| 4182 | if (null != displayResourceDll) | ||
| 4183 | { | ||
| 4184 | if (!displayResourceId.HasValue) | ||
| 4185 | { | ||
| 4186 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId")); | ||
| 4187 | } | ||
| 4188 | } | ||
| 4189 | else | ||
| 4190 | { | ||
| 4191 | if (displayResourceId.HasValue) | ||
| 4192 | { | ||
| 4193 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll")); | ||
| 4194 | } | ||
| 4195 | } | ||
| 4196 | |||
| 4197 | if (null == name) | ||
| 4198 | { | ||
| 4199 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 4200 | } | ||
| 4201 | |||
| 4202 | workingDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, workingDirectoryId, workingSubdirectory, "WorkingDirectory", "WorkingSubdirectory"); | ||
| 4203 | |||
| 4204 | if ("Component" != parentElementLocalName && null != target) | ||
| 4205 | { | ||
| 4206 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); | ||
| 4207 | } | ||
| 4208 | |||
| 4209 | if (null == id) | ||
| 4210 | { | ||
| 4211 | id = this.Core.CreateIdentifier("sct", directoryId, LowercaseOrNull(name)); | ||
| 4212 | } | ||
| 4213 | |||
| 4214 | foreach (var child in node.Elements()) | ||
| 4215 | { | ||
| 4216 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 4217 | { | ||
| 4218 | switch (child.Name.LocalName) | ||
| 4219 | { | ||
| 4220 | case "Icon": | ||
| 4221 | icon = this.ParseIconElement(child); | ||
| 4222 | break; | ||
| 4223 | case "ShortcutProperty": | ||
| 4224 | this.ParseShortcutPropertyElement(child, id.Id); | ||
| 4225 | break; | ||
| 4226 | default: | ||
| 4227 | this.Core.UnexpectedElement(node, child); | ||
| 4228 | break; | ||
| 4229 | } | ||
| 4230 | } | ||
| 4231 | else | ||
| 4232 | { | ||
| 4233 | this.Core.ParseExtensionElement(node, child); | ||
| 4234 | } | ||
| 4235 | } | ||
| 4236 | |||
| 4237 | if (!this.Core.EncounteredError) | ||
| 4238 | { | ||
| 4239 | if (advertise) | ||
| 4240 | { | ||
| 4241 | if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) | ||
| 4242 | { | ||
| 4243 | this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); | ||
| 4244 | } | ||
| 4245 | |||
| 4246 | target = Guid.Empty.ToString("B"); | ||
| 4247 | } | ||
| 4248 | else if (null != target) | ||
| 4249 | { | ||
| 4250 | } | ||
| 4251 | else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) | ||
| 4252 | { | ||
| 4253 | target = "[" + defaultTarget + "]"; | ||
| 4254 | } | ||
| 4255 | else if ("File" == parentElementLocalName) | ||
| 4256 | { | ||
| 4257 | target = "[#" + defaultTarget + "]"; | ||
| 4258 | } | ||
| 4259 | |||
| 4260 | this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) | ||
| 4261 | { | ||
| 4262 | DirectoryRef = directoryId, | ||
| 4263 | Name = name, | ||
| 4264 | ShortName = shortName, | ||
| 4265 | ComponentRef = componentId, | ||
| 4266 | Target = target, | ||
| 4267 | Arguments = arguments, | ||
| 4268 | Description = description, | ||
| 4269 | Hotkey = hotkey, | ||
| 4270 | IconRef = icon, | ||
| 4271 | IconIndex = iconIndex, | ||
| 4272 | Show = show, | ||
| 4273 | WorkingDirectory = workingDirectoryId, | ||
| 4274 | DisplayResourceDll = displayResourceDll, | ||
| 4275 | DisplayResourceId = displayResourceId, | ||
| 4276 | DescriptionResourceDll = descriptionResourceDll, | ||
| 4277 | DescriptionResourceId = descriptionResourceId, | ||
| 4278 | }); | ||
| 4279 | } | ||
| 4280 | } | ||
| 4281 | |||
| 4282 | /// <summary> | ||
| 4283 | /// Parses a shortcut property element. | ||
| 4284 | /// </summary> | ||
| 4285 | /// <param name="node">Element to parse.</param> | ||
| 4286 | /// <param name="shortcutId"></param> | ||
| 4287 | private void ParseShortcutPropertyElement(XElement node, string shortcutId) | ||
| 4288 | { | ||
| 4289 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4290 | Identifier id = null; | ||
| 4291 | string key = null; | ||
| 4292 | string value = null; | ||
| 4293 | |||
| 4294 | foreach (var attrib in node.Attributes()) | ||
| 4295 | { | ||
| 4296 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4297 | { | ||
| 4298 | switch (attrib.Name.LocalName) | ||
| 4299 | { | ||
| 4300 | case "Id": | ||
| 4301 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 4302 | break; | ||
| 4303 | case "Key": | ||
| 4304 | key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4305 | break; | ||
| 4306 | case "Value": | ||
| 4307 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4308 | break; | ||
| 4309 | default: | ||
| 4310 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4311 | break; | ||
| 4312 | } | ||
| 4313 | } | ||
| 4314 | else | ||
| 4315 | { | ||
| 4316 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4317 | } | ||
| 4318 | } | ||
| 4319 | |||
| 4320 | if (String.IsNullOrEmpty(key)) | ||
| 4321 | { | ||
| 4322 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); | ||
| 4323 | } | ||
| 4324 | else if (null == id) | ||
| 4325 | { | ||
| 4326 | id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); | ||
| 4327 | } | ||
| 4328 | |||
| 4329 | if (String.IsNullOrEmpty(value)) | ||
| 4330 | { | ||
| 4331 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 4332 | } | ||
| 4333 | |||
| 4334 | this.Core.ParseForExtensionElements(node); | ||
| 4335 | |||
| 4336 | if (!this.Core.EncounteredError) | ||
| 4337 | { | ||
| 4338 | this.Core.AddSymbol(new MsiShortcutPropertySymbol(sourceLineNumbers, id) | ||
| 4339 | { | ||
| 4340 | ShortcutRef = shortcutId, | ||
| 4341 | PropertyKey = key, | ||
| 4342 | PropVariantValue = value | ||
| 4343 | }); | ||
| 4344 | } | ||
| 4345 | } | ||
| 4346 | |||
| 4347 | /// <summary> | ||
| 4348 | /// Parses a typelib element. | ||
| 4349 | /// </summary> | ||
| 4350 | /// <param name="node">Element to parse.</param> | ||
| 4351 | /// <param name="componentId">Identifier of parent component.</param> | ||
| 4352 | /// <param name="fileServer">Identifier of file that acts as typelib server.</param> | ||
| 4353 | /// <param name="win64Component">true if the component is 64-bit.</param> | ||
| 4354 | private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component) | ||
| 4355 | { | ||
| 4356 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4357 | string id = null; | ||
| 4358 | var advertise = YesNoType.NotSet; | ||
| 4359 | var cost = CompilerConstants.IntegerNotSet; | ||
| 4360 | string description = null; | ||
| 4361 | var flags = 0; | ||
| 4362 | string helpDirectoryId = null; | ||
| 4363 | string helpSubdirectory = null; | ||
| 4364 | var language = CompilerConstants.IntegerNotSet; | ||
| 4365 | var majorVersion = CompilerConstants.IntegerNotSet; | ||
| 4366 | var minorVersion = CompilerConstants.IntegerNotSet; | ||
| 4367 | var resourceId = CompilerConstants.LongNotSet; | ||
| 4368 | |||
| 4369 | foreach (var attrib in node.Attributes()) | ||
| 4370 | { | ||
| 4371 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4372 | { | ||
| 4373 | switch (attrib.Name.LocalName) | ||
| 4374 | { | ||
| 4375 | case "Id": | ||
| 4376 | id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 4377 | break; | ||
| 4378 | case "Advertise": | ||
| 4379 | advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4380 | break; | ||
| 4381 | case "Control": | ||
| 4382 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 4383 | { | ||
| 4384 | flags |= 2; | ||
| 4385 | } | ||
| 4386 | break; | ||
| 4387 | case "Cost": | ||
| 4388 | cost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); | ||
| 4389 | break; | ||
| 4390 | case "Description": | ||
| 4391 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4392 | break; | ||
| 4393 | case "HasDiskImage": | ||
| 4394 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 4395 | { | ||
| 4396 | flags |= 8; | ||
| 4397 | } | ||
| 4398 | break; | ||
| 4399 | case "HelpDirectory": | ||
| 4400 | helpDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 4401 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, helpDirectoryId); | ||
| 4402 | break; | ||
| 4403 | case "HelpSubdirectory": | ||
| 4404 | helpSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); | ||
| 4405 | break; | ||
| 4406 | case "Hidden": | ||
| 4407 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 4408 | { | ||
| 4409 | flags |= 4; | ||
| 4410 | } | ||
| 4411 | break; | ||
| 4412 | case "Language": | ||
| 4413 | language = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 4414 | break; | ||
| 4415 | case "MajorVersion": | ||
| 4416 | majorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, UInt16.MaxValue); | ||
| 4417 | break; | ||
| 4418 | case "MinorVersion": | ||
| 4419 | minorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); | ||
| 4420 | break; | ||
| 4421 | case "ResourceId": | ||
| 4422 | resourceId = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, Int32.MinValue, Int32.MaxValue); | ||
| 4423 | break; | ||
| 4424 | case "Restricted": | ||
| 4425 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 4426 | { | ||
| 4427 | flags |= 1; | ||
| 4428 | } | ||
| 4429 | break; | ||
| 4430 | default: | ||
| 4431 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4432 | break; | ||
| 4433 | } | ||
| 4434 | } | ||
| 4435 | else | ||
| 4436 | { | ||
| 4437 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4438 | } | ||
| 4439 | } | ||
| 4440 | |||
| 4441 | if (null == id) | ||
| 4442 | { | ||
| 4443 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 4444 | } | ||
| 4445 | |||
| 4446 | if (CompilerConstants.IntegerNotSet == language) | ||
| 4447 | { | ||
| 4448 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); | ||
| 4449 | language = CompilerConstants.IllegalInteger; | ||
| 4450 | } | ||
| 4451 | |||
| 4452 | helpDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, helpDirectoryId, helpSubdirectory, "HelpDirectory", "HelpSubdirectory"); | ||
| 4453 | |||
| 4454 | // build up the typelib version string for the registry if the major or minor version was specified | ||
| 4455 | string registryVersion = null; | ||
| 4456 | if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) | ||
| 4457 | { | ||
| 4458 | if (CompilerConstants.IntegerNotSet != majorVersion) | ||
| 4459 | { | ||
| 4460 | registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat); | ||
| 4461 | } | ||
| 4462 | else | ||
| 4463 | { | ||
| 4464 | registryVersion = "0"; | ||
| 4465 | } | ||
| 4466 | |||
| 4467 | if (CompilerConstants.IntegerNotSet != minorVersion) | ||
| 4468 | { | ||
| 4469 | registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat)); | ||
| 4470 | } | ||
| 4471 | else | ||
| 4472 | { | ||
| 4473 | registryVersion = String.Concat(registryVersion, ".0"); | ||
| 4474 | } | ||
| 4475 | } | ||
| 4476 | |||
| 4477 | // if the advertise state has not been set, default to non-advertised | ||
| 4478 | if (YesNoType.NotSet == advertise) | ||
| 4479 | { | ||
| 4480 | advertise = YesNoType.No; | ||
| 4481 | } | ||
| 4482 | |||
| 4483 | foreach (var child in node.Elements()) | ||
| 4484 | { | ||
| 4485 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 4486 | { | ||
| 4487 | switch (child.Name.LocalName) | ||
| 4488 | { | ||
| 4489 | case "AppId": | ||
| 4490 | this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion); | ||
| 4491 | break; | ||
| 4492 | case "Class": | ||
| 4493 | this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null); | ||
| 4494 | break; | ||
| 4495 | case "Interface": | ||
| 4496 | this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion); | ||
| 4497 | break; | ||
| 4498 | default: | ||
| 4499 | this.Core.UnexpectedElement(node, child); | ||
| 4500 | break; | ||
| 4501 | } | ||
| 4502 | } | ||
| 4503 | else | ||
| 4504 | { | ||
| 4505 | this.Core.ParseExtensionElement(node, child); | ||
| 4506 | } | ||
| 4507 | } | ||
| 4508 | |||
| 4509 | |||
| 4510 | if (YesNoType.Yes == advertise) | ||
| 4511 | { | ||
| 4512 | if (CompilerConstants.LongNotSet != resourceId) | ||
| 4513 | { | ||
| 4514 | this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId")); | ||
| 4515 | } | ||
| 4516 | |||
| 4517 | if (0 != flags) | ||
| 4518 | { | ||
| 4519 | if (0x1 == (flags & 0x1)) | ||
| 4520 | { | ||
| 4521 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes")); | ||
| 4522 | } | ||
| 4523 | |||
| 4524 | if (0x2 == (flags & 0x2)) | ||
| 4525 | { | ||
| 4526 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes")); | ||
| 4527 | } | ||
| 4528 | |||
| 4529 | if (0x4 == (flags & 0x4)) | ||
| 4530 | { | ||
| 4531 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes")); | ||
| 4532 | } | ||
| 4533 | |||
| 4534 | if (0x8 == (flags & 0x8)) | ||
| 4535 | { | ||
| 4536 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes")); | ||
| 4537 | } | ||
| 4538 | } | ||
| 4539 | |||
| 4540 | if (!this.Core.EncounteredError) | ||
| 4541 | { | ||
| 4542 | var symbol = this.Core.AddSymbol(new TypeLibSymbol(sourceLineNumbers) | ||
| 4543 | { | ||
| 4544 | LibId = id, | ||
| 4545 | Language = language, | ||
| 4546 | ComponentRef = componentId, | ||
| 4547 | Description = description, | ||
| 4548 | DirectoryRef = helpDirectoryId, | ||
| 4549 | FeatureRef = Guid.Empty.ToString("B") | ||
| 4550 | }); | ||
| 4551 | |||
| 4552 | if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) | ||
| 4553 | { | ||
| 4554 | symbol.Version = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0); | ||
| 4555 | } | ||
| 4556 | |||
| 4557 | if (CompilerConstants.IntegerNotSet != cost) | ||
| 4558 | { | ||
| 4559 | symbol.Cost = cost; | ||
| 4560 | } | ||
| 4561 | } | ||
| 4562 | } | ||
| 4563 | else if (YesNoType.No == advertise) | ||
| 4564 | { | ||
| 4565 | if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost) | ||
| 4566 | { | ||
| 4567 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no")); | ||
| 4568 | } | ||
| 4569 | |||
| 4570 | if (null == fileServer) | ||
| 4571 | { | ||
| 4572 | this.Core.Write(ErrorMessages.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File")); | ||
| 4573 | } | ||
| 4574 | |||
| 4575 | if (null == registryVersion) | ||
| 4576 | { | ||
| 4577 | this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no")); | ||
| 4578 | } | ||
| 4579 | |||
| 4580 | // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description] | ||
| 4581 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId); | ||
| 4582 | |||
| 4583 | // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId] | ||
| 4584 | var path = String.Concat("[#", fileServer, "]"); | ||
| 4585 | if (CompilerConstants.LongNotSet != resourceId) | ||
| 4586 | { | ||
| 4587 | path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat)); | ||
| 4588 | } | ||
| 4589 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId); | ||
| 4590 | |||
| 4591 | // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] | ||
| 4592 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); | ||
| 4593 | |||
| 4594 | if (null != helpDirectoryId) | ||
| 4595 | { | ||
| 4596 | // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] | ||
| 4597 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectoryId, "]"), componentId); | ||
| 4598 | } | ||
| 4599 | } | ||
| 4600 | } | ||
| 4601 | |||
| 4602 | /// <summary> | ||
| 4603 | /// Parses an upgrade element. | ||
| 4604 | /// </summary> | ||
| 4605 | /// <param name="node">Element to parse.</param> | ||
| 4606 | private void ParseUpgradeElement(XElement node) | ||
| 4607 | { | ||
| 4608 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4609 | string id = null; | ||
| 4610 | |||
| 4611 | foreach (var attrib in node.Attributes()) | ||
| 4612 | { | ||
| 4613 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4614 | { | ||
| 4615 | switch (attrib.Name.LocalName) | ||
| 4616 | { | ||
| 4617 | case "Id": | ||
| 4618 | id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 4619 | break; | ||
| 4620 | default: | ||
| 4621 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4622 | break; | ||
| 4623 | } | ||
| 4624 | } | ||
| 4625 | else | ||
| 4626 | { | ||
| 4627 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4628 | } | ||
| 4629 | } | ||
| 4630 | |||
| 4631 | if (null == id) | ||
| 4632 | { | ||
| 4633 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 4634 | } | ||
| 4635 | |||
| 4636 | // process the UpgradeVersion children here | ||
| 4637 | foreach (var child in node.Elements()) | ||
| 4638 | { | ||
| 4639 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 4640 | { | ||
| 4641 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 4642 | |||
| 4643 | switch (child.Name.LocalName) | ||
| 4644 | { | ||
| 4645 | case "Property": | ||
| 4646 | this.ParsePropertyElement(child); | ||
| 4647 | this.Core.Write(WarningMessages.DeprecatedUpgradeProperty(childSourceLineNumbers)); | ||
| 4648 | break; | ||
| 4649 | case "UpgradeVersion": | ||
| 4650 | this.ParseUpgradeVersionElement(child, id); | ||
| 4651 | break; | ||
| 4652 | default: | ||
| 4653 | this.Core.UnexpectedElement(node, child); | ||
| 4654 | break; | ||
| 4655 | } | ||
| 4656 | } | ||
| 4657 | else | ||
| 4658 | { | ||
| 4659 | this.Core.ParseExtensionElement(node, child); | ||
| 4660 | } | ||
| 4661 | } | ||
| 4662 | |||
| 4663 | // No rows created here. All row creation is done in ParseUpgradeVersionElement. | ||
| 4664 | } | ||
| 4665 | |||
| 4666 | /// <summary> | ||
| 4667 | /// Parse upgrade version element. | ||
| 4668 | /// </summary> | ||
| 4669 | /// <param name="node">Element to parse.</param> | ||
| 4670 | /// <param name="upgradeId">Upgrade code.</param> | ||
| 4671 | private void ParseUpgradeVersionElement(XElement node, string upgradeId) | ||
| 4672 | { | ||
| 4673 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4674 | |||
| 4675 | string actionProperty = null; | ||
| 4676 | string language = null; | ||
| 4677 | string maximum = null; | ||
| 4678 | string minimum = null; | ||
| 4679 | var excludeLanguages = false; | ||
| 4680 | var ignoreFailures = false; | ||
| 4681 | var includeMax = false; | ||
| 4682 | var includeMin = true; | ||
| 4683 | var migrateFeatures = false; | ||
| 4684 | var onlyDetect = false; | ||
| 4685 | string removeFeatures = null; | ||
| 4686 | |||
| 4687 | foreach (var attrib in node.Attributes()) | ||
| 4688 | { | ||
| 4689 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4690 | { | ||
| 4691 | switch (attrib.Name.LocalName) | ||
| 4692 | { | ||
| 4693 | case "ExcludeLanguages": | ||
| 4694 | excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4695 | break; | ||
| 4696 | case "IgnoreRemoveFailure": | ||
| 4697 | ignoreFailures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4698 | break; | ||
| 4699 | case "IncludeMaximum": | ||
| 4700 | includeMax = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4701 | break; | ||
| 4702 | case "IncludeMinimum": // this is "yes" by default | ||
| 4703 | includeMin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4704 | break; | ||
| 4705 | case "Language": | ||
| 4706 | language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4707 | break; | ||
| 4708 | case "Minimum": | ||
| 4709 | minimum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 4710 | break; | ||
| 4711 | case "Maximum": | ||
| 4712 | maximum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 4713 | break; | ||
| 4714 | case "MigrateFeatures": | ||
| 4715 | migrateFeatures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4716 | break; | ||
| 4717 | case "OnlyDetect": | ||
| 4718 | onlyDetect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 4719 | break; | ||
| 4720 | case "Property": | ||
| 4721 | actionProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 4722 | break; | ||
| 4723 | case "RemoveFeatures": | ||
| 4724 | removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4725 | break; | ||
| 4726 | default: | ||
| 4727 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4728 | break; | ||
| 4729 | } | ||
| 4730 | } | ||
| 4731 | else | ||
| 4732 | { | ||
| 4733 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4734 | } | ||
| 4735 | } | ||
| 4736 | |||
| 4737 | if (null == actionProperty) | ||
| 4738 | { | ||
| 4739 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); | ||
| 4740 | } | ||
| 4741 | else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty) | ||
| 4742 | { | ||
| 4743 | this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty)); | ||
| 4744 | } | ||
| 4745 | |||
| 4746 | if (null == minimum && null == maximum) | ||
| 4747 | { | ||
| 4748 | this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); | ||
| 4749 | } | ||
| 4750 | |||
| 4751 | this.Core.ParseForExtensionElements(node); | ||
| 4752 | |||
| 4753 | if (!this.Core.EncounteredError) | ||
| 4754 | { | ||
| 4755 | this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) | ||
| 4756 | { | ||
| 4757 | UpgradeCode = upgradeId, | ||
| 4758 | VersionMin = minimum, | ||
| 4759 | VersionMax = maximum, | ||
| 4760 | Language = language, | ||
| 4761 | ExcludeLanguages = excludeLanguages, | ||
| 4762 | IgnoreRemoveFailures = ignoreFailures, | ||
| 4763 | VersionMaxInclusive = includeMax, | ||
| 4764 | VersionMinInclusive = includeMin, | ||
| 4765 | MigrateFeatures = migrateFeatures, | ||
| 4766 | OnlyDetect = onlyDetect, | ||
| 4767 | Remove = removeFeatures, | ||
| 4768 | ActionProperty = actionProperty | ||
| 4769 | }); | ||
| 4770 | |||
| 4771 | // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence | ||
| 4772 | // if at least one row in Upgrade table lacks the OnlyDetect attribute. | ||
| 4773 | if (!onlyDetect) | ||
| 4774 | { | ||
| 4775 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", "RemoveExistingProducts"); | ||
| 4776 | } | ||
| 4777 | } | ||
| 4778 | } | ||
| 4779 | |||
| 4780 | /// <summary> | ||
| 4781 | /// Parses a verb element. | ||
| 4782 | /// </summary> | ||
| 4783 | /// <param name="node">Element to parse.</param> | ||
| 4784 | /// <param name="extension">Extension verb is releated to.</param> | ||
| 4785 | /// <param name="progId">Optional progId for extension.</param> | ||
| 4786 | /// <param name="componentId">Identifier for parent component.</param> | ||
| 4787 | /// <param name="advertise">Flag if verb is advertised.</param> | ||
| 4788 | private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise) | ||
| 4789 | { | ||
| 4790 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4791 | string id = null; | ||
| 4792 | string argument = null; | ||
| 4793 | string command = null; | ||
| 4794 | var sequence = CompilerConstants.IntegerNotSet; | ||
| 4795 | string targetFile = null; | ||
| 4796 | string targetProperty = null; | ||
| 4797 | |||
| 4798 | foreach (var attrib in node.Attributes()) | ||
| 4799 | { | ||
| 4800 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4801 | { | ||
| 4802 | switch (attrib.Name.LocalName) | ||
| 4803 | { | ||
| 4804 | case "Id": | ||
| 4805 | id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4806 | break; | ||
| 4807 | case "Argument": | ||
| 4808 | argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4809 | break; | ||
| 4810 | case "Command": | ||
| 4811 | command = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4812 | break; | ||
| 4813 | case "Sequence": | ||
| 4814 | sequence = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 4815 | break; | ||
| 4816 | case "TargetFile": | ||
| 4817 | targetFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4818 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, targetFile); | ||
| 4819 | break; | ||
| 4820 | case "TargetProperty": | ||
| 4821 | targetProperty = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 4822 | break; | ||
| 4823 | default: | ||
| 4824 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4825 | break; | ||
| 4826 | } | ||
| 4827 | } | ||
| 4828 | else | ||
| 4829 | { | ||
| 4830 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4831 | } | ||
| 4832 | } | ||
| 4833 | |||
| 4834 | if (null == id) | ||
| 4835 | { | ||
| 4836 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 4837 | } | ||
| 4838 | |||
| 4839 | if (null != targetFile && null != targetProperty) | ||
| 4840 | { | ||
| 4841 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty")); | ||
| 4842 | } | ||
| 4843 | |||
| 4844 | this.Core.ParseForExtensionElements(node); | ||
| 4845 | |||
| 4846 | if (YesNoType.Yes == advertise) | ||
| 4847 | { | ||
| 4848 | if (null != targetFile) | ||
| 4849 | { | ||
| 4850 | this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile")); | ||
| 4851 | } | ||
| 4852 | |||
| 4853 | if (null != targetProperty) | ||
| 4854 | { | ||
| 4855 | this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty")); | ||
| 4856 | } | ||
| 4857 | |||
| 4858 | if (!this.Core.EncounteredError) | ||
| 4859 | { | ||
| 4860 | var symbol = this.Core.AddSymbol(new VerbSymbol(sourceLineNumbers) | ||
| 4861 | { | ||
| 4862 | ExtensionRef = extension, | ||
| 4863 | Verb = id, | ||
| 4864 | Command = command, | ||
| 4865 | Argument = argument, | ||
| 4866 | }); | ||
| 4867 | |||
| 4868 | if (CompilerConstants.IntegerNotSet != sequence) | ||
| 4869 | { | ||
| 4870 | symbol.Sequence = sequence; | ||
| 4871 | } | ||
| 4872 | } | ||
| 4873 | } | ||
| 4874 | else if (YesNoType.No == advertise) | ||
| 4875 | { | ||
| 4876 | if (CompilerConstants.IntegerNotSet != sequence) | ||
| 4877 | { | ||
| 4878 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no")); | ||
| 4879 | } | ||
| 4880 | |||
| 4881 | if (null == targetFile && null == targetProperty) | ||
| 4882 | { | ||
| 4883 | this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no")); | ||
| 4884 | } | ||
| 4885 | |||
| 4886 | string target = null; | ||
| 4887 | if (null != targetFile) | ||
| 4888 | { | ||
| 4889 | target = String.Concat("\"[#", targetFile, "]\""); | ||
| 4890 | } | ||
| 4891 | else if (null != targetProperty) | ||
| 4892 | { | ||
| 4893 | target = String.Concat("\"[", targetProperty, "]\""); | ||
| 4894 | } | ||
| 4895 | |||
| 4896 | if (null != argument) | ||
| 4897 | { | ||
| 4898 | target = String.Concat(target, " ", argument); | ||
| 4899 | } | ||
| 4900 | |||
| 4901 | var prefix = progId ?? String.Concat(".", extension); | ||
| 4902 | |||
| 4903 | if (null != command) | ||
| 4904 | { | ||
| 4905 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId); | ||
| 4906 | } | ||
| 4907 | |||
| 4908 | this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId); | ||
| 4909 | } | ||
| 4910 | } | ||
| 4911 | |||
| 4912 | /// <summary> | ||
| 4913 | /// Parses a WixVariable element. | ||
| 4914 | /// </summary> | ||
| 4915 | /// <param name="node">Element to parse.</param> | ||
| 4916 | private void ParseWixVariableElement(XElement node) | ||
| 4917 | { | ||
| 4918 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 4919 | Identifier id = null; | ||
| 4920 | var overridable = false; | ||
| 4921 | string value = null; | ||
| 4922 | |||
| 4923 | foreach (var attrib in node.Attributes()) | ||
| 4924 | { | ||
| 4925 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 4926 | { | ||
| 4927 | switch (attrib.Name.LocalName) | ||
| 4928 | { | ||
| 4929 | case "Id": | ||
| 4930 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 4931 | break; | ||
| 4932 | case "Overridable": | ||
| 4933 | overridable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
| 4934 | break; | ||
| 4935 | case "Value": | ||
| 4936 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | ||
| 4937 | break; | ||
| 4938 | default: | ||
| 4939 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 4940 | break; | ||
| 4941 | } | ||
| 4942 | } | ||
| 4943 | else | ||
| 4944 | { | ||
| 4945 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 4946 | } | ||
| 4947 | } | ||
| 4948 | |||
| 4949 | if (null == id) | ||
| 4950 | { | ||
| 4951 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 4952 | } | ||
| 4953 | |||
| 4954 | if (null == value) | ||
| 4955 | { | ||
| 4956 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 4957 | } | ||
| 4958 | |||
| 4959 | this.Core.ParseForExtensionElements(node); | ||
| 4960 | |||
| 4961 | if (!this.Core.EncounteredError) | ||
| 4962 | { | ||
| 4963 | this.Core.AddSymbol(new WixVariableSymbol(sourceLineNumbers, id) | ||
| 4964 | { | ||
| 4965 | Value = value, | ||
| 4966 | Overridable = overridable | ||
| 4967 | }); | ||
| 4968 | } | ||
| 4969 | } | ||
| 4970 | |||
| 4971 | private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 4972 | { | ||
| 4973 | var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 4974 | switch (compressionLevel) | ||
| 4975 | { | ||
| 4976 | case "high": | ||
| 4977 | return CompressionLevel.High; | ||
| 4978 | case "low": | ||
| 4979 | return CompressionLevel.Low; | ||
| 4980 | case "medium": | ||
| 4981 | return CompressionLevel.Medium; | ||
| 4982 | case "mszip": | ||
| 4983 | return CompressionLevel.Mszip; | ||
| 4984 | case "none": | ||
| 4985 | return CompressionLevel.None; | ||
| 4986 | case "": | ||
| 4987 | break; | ||
| 4988 | default: | ||
| 4989 | this.Core.Write(ErrorMessages.IllegalCompressionLevel(sourceLineNumbers, compressionLevel)); | ||
| 4990 | break; | ||
| 4991 | } | ||
| 4992 | |||
| 4993 | return null; | ||
| 4994 | } | ||
| 4995 | } | ||
| 4996 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler_Patch.cs b/src/wix/WixToolset.Core/Compiler_Patch.cs new file mode 100644 index 00000000..c9cae183 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Patch.cs | |||
| @@ -0,0 +1,657 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.Xml.Linq; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | using WixToolset.Extensibility; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Compiler of the WiX toolset. | ||
| 16 | /// </summary> | ||
| 17 | internal partial class Compiler : ICompiler | ||
| 18 | { | ||
| 19 | /// <summary> | ||
| 20 | /// Parses an patch element. | ||
| 21 | /// </summary> | ||
| 22 | /// <param name="node">The element to parse.</param> | ||
| 23 | private void ParsePatchElement(XElement node) | ||
| 24 | { | ||
| 25 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 26 | string patchId = null; | ||
| 27 | string codepage = null; | ||
| 28 | ////bool versionMismatches = false; | ||
| 29 | ////bool productMismatches = false; | ||
| 30 | var allowRemoval = false; | ||
| 31 | string classification = null; | ||
| 32 | string clientPatchId = null; | ||
| 33 | string description = null; | ||
| 34 | string displayName = null; | ||
| 35 | string comments = null; | ||
| 36 | string manufacturer = null; | ||
| 37 | var minorUpdateTargetRTM = YesNoType.NotSet; | ||
| 38 | string moreInfoUrl = null; | ||
| 39 | var optimizeCA = CompilerConstants.IntegerNotSet; | ||
| 40 | var optimizedInstallMode = YesNoType.NotSet; | ||
| 41 | string targetProductName = null; | ||
| 42 | // string replaceGuids = String.Empty; | ||
| 43 | var apiPatchingSymbolFlags = 0; | ||
| 44 | var optimizePatchSizeForLargeFiles = false; | ||
| 45 | |||
| 46 | foreach (var attrib in node.Attributes()) | ||
| 47 | { | ||
| 48 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 49 | { | ||
| 50 | switch (attrib.Name.LocalName) | ||
| 51 | { | ||
| 52 | case "Id": | ||
| 53 | patchId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); | ||
| 54 | break; | ||
| 55 | case "Codepage": | ||
| 56 | codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); | ||
| 57 | break; | ||
| 58 | case "AllowMajorVersionMismatches": | ||
| 59 | ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
| 60 | break; | ||
| 61 | case "AllowProductCodeMismatches": | ||
| 62 | ////productMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
| 63 | break; | ||
| 64 | case "AllowRemoval": | ||
| 65 | allowRemoval = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
| 66 | break; | ||
| 67 | case "Classification": | ||
| 68 | classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 69 | break; | ||
| 70 | case "ClientPatchId": | ||
| 71 | clientPatchId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 72 | break; | ||
| 73 | case "Description": | ||
| 74 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 75 | break; | ||
| 76 | case "DisplayName": | ||
| 77 | displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 78 | break; | ||
| 79 | case "Comments": | ||
| 80 | comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 81 | break; | ||
| 82 | case "Manufacturer": | ||
| 83 | manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 84 | break; | ||
| 85 | case "MinorUpdateTargetRTM": | ||
| 86 | minorUpdateTargetRTM = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 87 | break; | ||
| 88 | case "MoreInfoURL": | ||
| 89 | moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 90 | break; | ||
| 91 | case "OptimizedInstallMode": | ||
| 92 | optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 93 | break; | ||
| 94 | case "TargetProductName": | ||
| 95 | targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 96 | break; | ||
| 97 | case "ApiPatchingSymbolNoImagehlpFlag": | ||
| 98 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoImagehlp : 0; | ||
| 99 | break; | ||
| 100 | case "ApiPatchingSymbolNoFailuresFlag": | ||
| 101 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoFailures : 0; | ||
| 102 | break; | ||
| 103 | case "ApiPatchingSymbolUndecoratedTooFlag": | ||
| 104 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolUndecoratedToo : 0; | ||
| 105 | break; | ||
| 106 | case "OptimizePatchSizeForLargeFiles": | ||
| 107 | optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | ||
| 108 | break; | ||
| 109 | default: | ||
| 110 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 111 | break; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | else | ||
| 115 | { | ||
| 116 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | if (patchId == null || patchId == "*") | ||
| 121 | { | ||
| 122 | // auto-generate at compile time, since this value gets dispersed to several locations | ||
| 123 | patchId = Common.GenerateGuid(); | ||
| 124 | } | ||
| 125 | this.activeName = patchId; | ||
| 126 | |||
| 127 | if (null == this.activeName) | ||
| 128 | { | ||
| 129 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 130 | } | ||
| 131 | if (null == classification) | ||
| 132 | { | ||
| 133 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); | ||
| 134 | } | ||
| 135 | if (null == clientPatchId) | ||
| 136 | { | ||
| 137 | clientPatchId = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); | ||
| 138 | } | ||
| 139 | if (null == description) | ||
| 140 | { | ||
| 141 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); | ||
| 142 | } | ||
| 143 | if (null == displayName) | ||
| 144 | { | ||
| 145 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); | ||
| 146 | } | ||
| 147 | if (null == manufacturer) | ||
| 148 | { | ||
| 149 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); | ||
| 150 | } | ||
| 151 | |||
| 152 | this.Core.CreateActiveSection(this.activeName, SectionType.Patch, this.Context.CompilationId); | ||
| 153 | |||
| 154 | foreach (var child in node.Elements()) | ||
| 155 | { | ||
| 156 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 157 | { | ||
| 158 | switch (child.Name.LocalName) | ||
| 159 | { | ||
| 160 | case "PatchInformation": | ||
| 161 | this.ParsePatchInformationElement(child); | ||
| 162 | break; | ||
| 163 | case "Media": | ||
| 164 | this.ParseMediaElement(child, patchId); | ||
| 165 | break; | ||
| 166 | case "OptimizeCustomActions": | ||
| 167 | optimizeCA = this.ParseOptimizeCustomActionsElement(child); | ||
| 168 | break; | ||
| 169 | case "PatchFamily": | ||
| 170 | this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Patch, patchId); | ||
| 171 | break; | ||
| 172 | case "PatchFamilyRef": | ||
| 173 | this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.Patch, patchId); | ||
| 174 | break; | ||
| 175 | case "PatchFamilyGroup": | ||
| 176 | this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Patch, patchId); | ||
| 177 | break; | ||
| 178 | case "PatchFamilyGroupRef": | ||
| 179 | this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Patch, patchId); | ||
| 180 | break; | ||
| 181 | case "PatchProperty": | ||
| 182 | this.ParsePatchPropertyElement(child, true); | ||
| 183 | break; | ||
| 184 | case "TargetProductCodes": | ||
| 185 | this.ParseTargetProductCodesElement(child); | ||
| 186 | break; | ||
| 187 | default: | ||
| 188 | this.Core.UnexpectedElement(node, child); | ||
| 189 | break; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | else | ||
| 193 | { | ||
| 194 | this.Core.ParseExtensionElement(node, child); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | if (!this.Core.EncounteredError) | ||
| 199 | { | ||
| 200 | this.Core.AddSymbol(new WixPatchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, patchId)) | ||
| 201 | { | ||
| 202 | Codepage = codepage, | ||
| 203 | ClientPatchId = clientPatchId, | ||
| 204 | OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles, | ||
| 205 | ApiPatchingSymbolFlags = apiPatchingSymbolFlags, | ||
| 206 | }); | ||
| 207 | |||
| 208 | if (allowRemoval) | ||
| 209 | { | ||
| 210 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "AllowRemoval", allowRemoval ? "1" : "0"); | ||
| 211 | } | ||
| 212 | |||
| 213 | if (null != classification) | ||
| 214 | { | ||
| 215 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "Classification", classification); | ||
| 216 | } | ||
| 217 | |||
| 218 | // always generate the CreationTimeUTC | ||
| 219 | { | ||
| 220 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", DateTime.UtcNow.ToString("MM-dd-yy HH:mm", CultureInfo.InvariantCulture)); | ||
| 221 | } | ||
| 222 | |||
| 223 | if (null != description) | ||
| 224 | { | ||
| 225 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "Description", description); | ||
| 226 | } | ||
| 227 | |||
| 228 | if (null != displayName) | ||
| 229 | { | ||
| 230 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); | ||
| 231 | } | ||
| 232 | |||
| 233 | if (null != manufacturer) | ||
| 234 | { | ||
| 235 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturer); | ||
| 236 | } | ||
| 237 | |||
| 238 | if (YesNoType.NotSet != minorUpdateTargetRTM) | ||
| 239 | { | ||
| 240 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", YesNoType.Yes == minorUpdateTargetRTM ? "1" : "0"); | ||
| 241 | } | ||
| 242 | |||
| 243 | if (null != moreInfoUrl) | ||
| 244 | { | ||
| 245 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); | ||
| 246 | } | ||
| 247 | |||
| 248 | if (CompilerConstants.IntegerNotSet != optimizeCA) | ||
| 249 | { | ||
| 250 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); | ||
| 251 | } | ||
| 252 | |||
| 253 | if (YesNoType.NotSet != optimizedInstallMode) | ||
| 254 | { | ||
| 255 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); | ||
| 256 | } | ||
| 257 | |||
| 258 | if (null != targetProductName) | ||
| 259 | { | ||
| 260 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); | ||
| 261 | } | ||
| 262 | |||
| 263 | if (null != comments) | ||
| 264 | { | ||
| 265 | this.AddMsiPatchMetadata(sourceLineNumbers, null, "Comments", comments); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | // TODO: do something with versionMismatches and productMismatches | ||
| 269 | } | ||
| 270 | |||
| 271 | /// <summary> | ||
| 272 | /// Parses the OptimizeCustomActions element. | ||
| 273 | /// </summary> | ||
| 274 | /// <param name="node">Element to parse.</param> | ||
| 275 | /// <returns>The combined integer value for callers to store as appropriate.</returns> | ||
| 276 | private int ParseOptimizeCustomActionsElement(XElement node) | ||
| 277 | { | ||
| 278 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 279 | var optimizeCA = OptimizeCAFlags.None; | ||
| 280 | |||
| 281 | foreach (var attrib in node.Attributes()) | ||
| 282 | { | ||
| 283 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 284 | { | ||
| 285 | switch (attrib.Name.LocalName) | ||
| 286 | { | ||
| 287 | case "SkipAssignment": | ||
| 288 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 289 | { | ||
| 290 | optimizeCA |= OptimizeCAFlags.SkipAssignment; | ||
| 291 | } | ||
| 292 | break; | ||
| 293 | case "SkipImmediate": | ||
| 294 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 295 | { | ||
| 296 | optimizeCA |= OptimizeCAFlags.SkipImmediate; | ||
| 297 | } | ||
| 298 | break; | ||
| 299 | case "SkipDeferred": | ||
| 300 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 301 | { | ||
| 302 | optimizeCA |= OptimizeCAFlags.SkipDeferred; | ||
| 303 | } | ||
| 304 | break; | ||
| 305 | default: | ||
| 306 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 307 | break; | ||
| 308 | } | ||
| 309 | } | ||
| 310 | else | ||
| 311 | { | ||
| 312 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 313 | } | ||
| 314 | } | ||
| 315 | |||
| 316 | return (int)optimizeCA; | ||
| 317 | } | ||
| 318 | |||
| 319 | /// <summary> | ||
| 320 | /// Parses a PatchFamily element. | ||
| 321 | /// </summary> | ||
| 322 | /// <param name="node">The element to parse.</param> | ||
| 323 | /// <param name="parentType"></param> | ||
| 324 | /// <param name="parentId"></param> | ||
| 325 | private void ParsePatchFamilyElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 326 | { | ||
| 327 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 328 | Identifier id = null; | ||
| 329 | string productCode = null; | ||
| 330 | string version = null; | ||
| 331 | var attributes = 0; | ||
| 332 | |||
| 333 | foreach (var attrib in node.Attributes()) | ||
| 334 | { | ||
| 335 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 336 | { | ||
| 337 | switch (attrib.Name.LocalName) | ||
| 338 | { | ||
| 339 | case "Id": | ||
| 340 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 341 | break; | ||
| 342 | case "ProductCode": | ||
| 343 | productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 344 | break; | ||
| 345 | case "Version": | ||
| 346 | version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 347 | break; | ||
| 348 | case "Supersede": | ||
| 349 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 350 | { | ||
| 351 | attributes |= 0x1; | ||
| 352 | } | ||
| 353 | break; | ||
| 354 | default: | ||
| 355 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 356 | break; | ||
| 357 | } | ||
| 358 | } | ||
| 359 | else | ||
| 360 | { | ||
| 361 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | if (null == id) | ||
| 366 | { | ||
| 367 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 368 | id = Identifier.Invalid; | ||
| 369 | } | ||
| 370 | |||
| 371 | if (String.IsNullOrEmpty(version)) | ||
| 372 | { | ||
| 373 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); | ||
| 374 | } | ||
| 375 | else if (!CompilerCore.IsValidProductVersion(version)) | ||
| 376 | { | ||
| 377 | this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); | ||
| 378 | } | ||
| 379 | |||
| 380 | // find unexpected child elements | ||
| 381 | foreach (var child in node.Elements()) | ||
| 382 | { | ||
| 383 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 384 | { | ||
| 385 | switch (child.Name.LocalName) | ||
| 386 | { | ||
| 387 | case "All": | ||
| 388 | this.ParseAllElement(child); | ||
| 389 | break; | ||
| 390 | case "BinaryRef": | ||
| 391 | this.ParsePatchChildRefElement(child, "Binary"); | ||
| 392 | break; | ||
| 393 | case "ComponentRef": | ||
| 394 | this.ParsePatchChildRefElement(child, "Component"); | ||
| 395 | break; | ||
| 396 | case "CustomActionRef": | ||
| 397 | this.ParsePatchChildRefElement(child, "CustomAction"); | ||
| 398 | break; | ||
| 399 | case "DirectoryRef": | ||
| 400 | this.ParsePatchChildRefElement(child, "Directory"); | ||
| 401 | break; | ||
| 402 | case "DigitalCertificateRef": | ||
| 403 | this.ParsePatchChildRefElement(child, "MsiDigitalCertificate"); | ||
| 404 | break; | ||
| 405 | case "FeatureRef": | ||
| 406 | this.ParsePatchChildRefElement(child, "Feature"); | ||
| 407 | break; | ||
| 408 | case "IconRef": | ||
| 409 | this.ParsePatchChildRefElement(child, "Icon"); | ||
| 410 | break; | ||
| 411 | case "PropertyRef": | ||
| 412 | this.ParsePatchChildRefElement(child, "Property"); | ||
| 413 | break; | ||
| 414 | case "SoftwareTagRef": | ||
| 415 | this.ParseTagRefElement(child); | ||
| 416 | break; | ||
| 417 | case "UIRef": | ||
| 418 | this.ParsePatchChildRefElement(child, "WixUI"); | ||
| 419 | break; | ||
| 420 | default: | ||
| 421 | this.Core.UnexpectedElement(node, child); | ||
| 422 | break; | ||
| 423 | } | ||
| 424 | } | ||
| 425 | else | ||
| 426 | { | ||
| 427 | this.Core.ParseExtensionElement(node, child); | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | |||
| 432 | if (!this.Core.EncounteredError) | ||
| 433 | { | ||
| 434 | this.Core.AddSymbol(new MsiPatchSequenceSymbol(sourceLineNumbers) | ||
| 435 | { | ||
| 436 | PatchFamily = id.Id, | ||
| 437 | ProductCode = productCode, | ||
| 438 | Sequence = version, | ||
| 439 | Attributes = attributes | ||
| 440 | }); | ||
| 441 | |||
| 442 | if (ComplexReferenceParentType.Unknown != parentType) | ||
| 443 | { | ||
| 444 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, id.Id, ComplexReferenceParentType.Patch == parentType); | ||
| 445 | } | ||
| 446 | } | ||
| 447 | } | ||
| 448 | |||
| 449 | /// <summary> | ||
| 450 | /// Parses a PatchFamilyGroup element. | ||
| 451 | /// </summary> | ||
| 452 | /// <param name="node">Element to parse.</param> | ||
| 453 | /// <param name="parentType"></param> | ||
| 454 | /// <param name="parentId"></param> | ||
| 455 | private void ParsePatchFamilyGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 456 | { | ||
| 457 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 458 | Identifier id = null; | ||
| 459 | |||
| 460 | foreach (var attrib in node.Attributes()) | ||
| 461 | { | ||
| 462 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 463 | { | ||
| 464 | switch (attrib.Name.LocalName) | ||
| 465 | { | ||
| 466 | case "Id": | ||
| 467 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 468 | break; | ||
| 469 | default: | ||
| 470 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 471 | break; | ||
| 472 | } | ||
| 473 | } | ||
| 474 | else | ||
| 475 | { | ||
| 476 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 477 | } | ||
| 478 | } | ||
| 479 | |||
| 480 | if (null == id) | ||
| 481 | { | ||
| 482 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 483 | id = Identifier.Invalid; | ||
| 484 | } | ||
| 485 | |||
| 486 | foreach (var child in node.Elements()) | ||
| 487 | { | ||
| 488 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 489 | { | ||
| 490 | switch (child.Name.LocalName) | ||
| 491 | { | ||
| 492 | case "PatchFamily": | ||
| 493 | this.ParsePatchFamilyElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); | ||
| 494 | break; | ||
| 495 | case "PatchFamilyRef": | ||
| 496 | this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); | ||
| 497 | break; | ||
| 498 | case "PatchFamilyGroupRef": | ||
| 499 | this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); | ||
| 500 | break; | ||
| 501 | default: | ||
| 502 | this.Core.UnexpectedElement(node, child); | ||
| 503 | break; | ||
| 504 | } | ||
| 505 | } | ||
| 506 | else | ||
| 507 | { | ||
| 508 | this.Core.ParseExtensionElement(node, child); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | if (!this.Core.EncounteredError) | ||
| 513 | { | ||
| 514 | this.Core.AddSymbol(new WixPatchFamilyGroupSymbol(sourceLineNumbers, id)); | ||
| 515 | |||
| 516 | //Add this PatchFamilyGroup and its parent in WixGroup. | ||
| 517 | this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PatchFamilyGroup, id.Id); | ||
| 518 | } | ||
| 519 | } | ||
| 520 | |||
| 521 | /// <summary> | ||
| 522 | /// Parses a PatchFamilyGroup reference element. | ||
| 523 | /// </summary> | ||
| 524 | /// <param name="node">Element to parse.</param> | ||
| 525 | /// <param name="parentType">The type of parent.</param> | ||
| 526 | /// <param name="parentId">Identifier of parent element.</param> | ||
| 527 | private void ParsePatchFamilyGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) | ||
| 528 | { | ||
| 529 | Debug.Assert(ComplexReferenceParentType.PatchFamilyGroup == parentType || ComplexReferenceParentType.Patch == parentType); | ||
| 530 | |||
| 531 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 532 | string id = null; | ||
| 533 | |||
| 534 | foreach (var attrib in node.Attributes()) | ||
| 535 | { | ||
| 536 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 537 | { | ||
| 538 | switch (attrib.Name.LocalName) | ||
| 539 | { | ||
| 540 | case "Id": | ||
| 541 | id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 542 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixPatchFamilyGroup, id); | ||
| 543 | break; | ||
| 544 | default: | ||
| 545 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 546 | break; | ||
| 547 | } | ||
| 548 | } | ||
| 549 | else | ||
| 550 | { | ||
| 551 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 552 | } | ||
| 553 | } | ||
| 554 | |||
| 555 | if (null == id) | ||
| 556 | { | ||
| 557 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 558 | } | ||
| 559 | |||
| 560 | this.Core.ParseForExtensionElements(node); | ||
| 561 | |||
| 562 | if (!this.Core.EncounteredError) | ||
| 563 | { | ||
| 564 | this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamilyGroup, id, true); | ||
| 565 | } | ||
| 566 | } | ||
| 567 | |||
| 568 | /// <summary> | ||
| 569 | /// Parses a TargetProductCodes element. | ||
| 570 | /// </summary> | ||
| 571 | /// <param name="node">The element to parse.</param> | ||
| 572 | private void ParseTargetProductCodesElement(XElement node) | ||
| 573 | { | ||
| 574 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 575 | var replace = false; | ||
| 576 | var targetProductCodes = new List<string>(); | ||
| 577 | |||
| 578 | foreach (var attrib in node.Attributes()) | ||
| 579 | { | ||
| 580 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 581 | { | ||
| 582 | switch (attrib.Name.LocalName) | ||
| 583 | { | ||
| 584 | case "Replace": | ||
| 585 | replace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 586 | break; | ||
| 587 | default: | ||
| 588 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 589 | break; | ||
| 590 | } | ||
| 591 | } | ||
| 592 | else | ||
| 593 | { | ||
| 594 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 595 | } | ||
| 596 | } | ||
| 597 | |||
| 598 | foreach (var child in node.Elements()) | ||
| 599 | { | ||
| 600 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 601 | { | ||
| 602 | switch (child.Name.LocalName) | ||
| 603 | { | ||
| 604 | case "TargetProductCode": | ||
| 605 | var id = this.ParseTargetProductCodeElement(child); | ||
| 606 | if (0 == String.CompareOrdinal("*", id)) | ||
| 607 | { | ||
| 608 | this.Core.Write(ErrorMessages.IllegalAttributeValueWhenNested(sourceLineNumbers, child.Name.LocalName, "Id", id, node.Name.LocalName)); | ||
| 609 | } | ||
| 610 | else | ||
| 611 | { | ||
| 612 | targetProductCodes.Add(id); | ||
| 613 | } | ||
| 614 | break; | ||
| 615 | default: | ||
| 616 | this.Core.UnexpectedElement(node, child); | ||
| 617 | break; | ||
| 618 | } | ||
| 619 | } | ||
| 620 | else | ||
| 621 | { | ||
| 622 | this.Core.ParseExtensionElement(node, child); | ||
| 623 | } | ||
| 624 | } | ||
| 625 | |||
| 626 | if (!this.Core.EncounteredError) | ||
| 627 | { | ||
| 628 | // By default, target ProductCodes should be added. | ||
| 629 | if (!replace) | ||
| 630 | { | ||
| 631 | this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) | ||
| 632 | { | ||
| 633 | ProductCode = "*" | ||
| 634 | }); | ||
| 635 | } | ||
| 636 | |||
| 637 | foreach (var targetProductCode in targetProductCodes) | ||
| 638 | { | ||
| 639 | this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) | ||
| 640 | { | ||
| 641 | ProductCode = targetProductCode | ||
| 642 | }); | ||
| 643 | } | ||
| 644 | } | ||
| 645 | } | ||
| 646 | |||
| 647 | private void AddMsiPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) | ||
| 648 | { | ||
| 649 | this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) | ||
| 650 | { | ||
| 651 | Company = company, | ||
| 652 | Property = property, | ||
| 653 | Value = value | ||
| 654 | }); | ||
| 655 | } | ||
| 656 | } | ||
| 657 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler_PatchCreation.cs b/src/wix/WixToolset.Core/Compiler_PatchCreation.cs new file mode 100644 index 00000000..81ae4121 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_PatchCreation.cs | |||
| @@ -0,0 +1,1265 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Xml.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Extensibility; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// Compiler of the WiX toolset. | ||
| 15 | /// </summary> | ||
| 16 | internal partial class Compiler : ICompiler | ||
| 17 | { | ||
| 18 | /// <summary> | ||
| 19 | /// Parses a patch creation element. | ||
| 20 | /// </summary> | ||
| 21 | /// <param name="node">The element to parse.</param> | ||
| 22 | private void ParsePatchCreationElement(XElement node) | ||
| 23 | { | ||
| 24 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 25 | var clean = true; // Default is to clean | ||
| 26 | var codepage = 0; | ||
| 27 | string outputPath = null; | ||
| 28 | var productMismatches = false; | ||
| 29 | var replaceGuids = String.Empty; | ||
| 30 | string sourceList = null; | ||
| 31 | string symbolFlags = null; | ||
| 32 | var targetProducts = String.Empty; | ||
| 33 | var versionMismatches = false; | ||
| 34 | var wholeFiles = false; | ||
| 35 | |||
| 36 | foreach (var attrib in node.Attributes()) | ||
| 37 | { | ||
| 38 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 39 | { | ||
| 40 | switch (attrib.Name.LocalName) | ||
| 41 | { | ||
| 42 | case "Id": | ||
| 43 | this.activeName = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 44 | break; | ||
| 45 | case "AllowMajorVersionMismatches": | ||
| 46 | versionMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 47 | break; | ||
| 48 | case "AllowProductCodeMismatches": | ||
| 49 | productMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 50 | break; | ||
| 51 | case "CleanWorkingFolder": | ||
| 52 | clean = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 53 | break; | ||
| 54 | case "Codepage": | ||
| 55 | codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); | ||
| 56 | break; | ||
| 57 | case "OutputPath": | ||
| 58 | outputPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 59 | break; | ||
| 60 | case "SourceList": | ||
| 61 | sourceList = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 62 | break; | ||
| 63 | case "SymbolFlags": | ||
| 64 | symbolFlags = String.Format(CultureInfo.InvariantCulture, "0x{0:x8}", this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, UInt32.MaxValue)); | ||
| 65 | break; | ||
| 66 | case "WholeFilesOnly": | ||
| 67 | wholeFiles = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 68 | break; | ||
| 69 | default: | ||
| 70 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 71 | break; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | else | ||
| 75 | { | ||
| 76 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | if (null == this.activeName) | ||
| 81 | { | ||
| 82 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 83 | } | ||
| 84 | |||
| 85 | this.Core.CreateActiveSection(this.activeName, SectionType.PatchCreation, this.Context.CompilationId); | ||
| 86 | |||
| 87 | foreach (var child in node.Elements()) | ||
| 88 | { | ||
| 89 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 90 | { | ||
| 91 | switch (child.Name.LocalName) | ||
| 92 | { | ||
| 93 | case "Family": | ||
| 94 | this.ParseFamilyElement(child); | ||
| 95 | break; | ||
| 96 | case "PatchInformation": | ||
| 97 | this.ParsePatchInformationElement(child); | ||
| 98 | break; | ||
| 99 | case "PatchMetadata": | ||
| 100 | this.ParsePatchMetadataElement(child); | ||
| 101 | break; | ||
| 102 | case "PatchProperty": | ||
| 103 | this.ParsePatchPropertyElement(child, false); | ||
| 104 | break; | ||
| 105 | case "PatchSequence": | ||
| 106 | this.ParsePatchSequenceElement(child); | ||
| 107 | break; | ||
| 108 | case "ReplacePatch": | ||
| 109 | replaceGuids = String.Concat(replaceGuids, this.ParseReplacePatchElement(child)); | ||
| 110 | break; | ||
| 111 | case "TargetProductCode": | ||
| 112 | var targetProduct = this.ParseTargetProductCodeElement(child); | ||
| 113 | if (0 < targetProducts.Length) | ||
| 114 | { | ||
| 115 | targetProducts = String.Concat(targetProducts, ";"); | ||
| 116 | } | ||
| 117 | targetProducts = String.Concat(targetProducts, targetProduct); | ||
| 118 | break; | ||
| 119 | default: | ||
| 120 | this.Core.UnexpectedElement(node, child); | ||
| 121 | break; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | else | ||
| 125 | { | ||
| 126 | this.Core.ParseExtensionElement(node, child); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | this.AddPrivateProperty(sourceLineNumbers, "PatchGUID", this.activeName); | ||
| 131 | this.AddPrivateProperty(sourceLineNumbers, "AllowProductCodeMismatches", productMismatches ? "1" : "0"); | ||
| 132 | this.AddPrivateProperty(sourceLineNumbers, "AllowProductVersionMajorMismatches", versionMismatches ? "1" : "0"); | ||
| 133 | this.AddPrivateProperty(sourceLineNumbers, "DontRemoveTempFolderWhenFinished", clean ? "0" : "1"); | ||
| 134 | this.AddPrivateProperty(sourceLineNumbers, "IncludeWholeFilesOnly", wholeFiles ? "1" : "0"); | ||
| 135 | |||
| 136 | if (null != symbolFlags) | ||
| 137 | { | ||
| 138 | this.AddPrivateProperty(sourceLineNumbers, "ApiPatchingSymbolFlags", symbolFlags); | ||
| 139 | } | ||
| 140 | |||
| 141 | if (0 < replaceGuids.Length) | ||
| 142 | { | ||
| 143 | this.AddPrivateProperty(sourceLineNumbers, "ListOfPatchGUIDsToReplace", replaceGuids); | ||
| 144 | } | ||
| 145 | |||
| 146 | if (0 < targetProducts.Length) | ||
| 147 | { | ||
| 148 | this.AddPrivateProperty(sourceLineNumbers, "ListOfTargetProductCodes", targetProducts); | ||
| 149 | } | ||
| 150 | |||
| 151 | if (null != outputPath) | ||
| 152 | { | ||
| 153 | this.AddPrivateProperty(sourceLineNumbers, "PatchOutputPath", outputPath); | ||
| 154 | } | ||
| 155 | |||
| 156 | if (null != sourceList) | ||
| 157 | { | ||
| 158 | this.AddPrivateProperty(sourceLineNumbers, "PatchSourceList", sourceList); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | /// <summary> | ||
| 163 | /// Parses a family element. | ||
| 164 | /// </summary> | ||
| 165 | /// <param name="node">The element to parse.</param> | ||
| 166 | private void ParseFamilyElement(XElement node) | ||
| 167 | { | ||
| 168 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 169 | var diskId = CompilerConstants.IntegerNotSet; | ||
| 170 | string diskPrompt = null; | ||
| 171 | string mediaSrcProp = null; | ||
| 172 | string name = null; | ||
| 173 | var sequenceStart = CompilerConstants.IntegerNotSet; | ||
| 174 | string volumeLabel = null; | ||
| 175 | |||
| 176 | foreach (var attrib in node.Attributes()) | ||
| 177 | { | ||
| 178 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 179 | { | ||
| 180 | switch (attrib.Name.LocalName) | ||
| 181 | { | ||
| 182 | case "DiskId": | ||
| 183 | diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); | ||
| 184 | break; | ||
| 185 | case "DiskPrompt": | ||
| 186 | diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 187 | break; | ||
| 188 | case "MediaSrcProp": | ||
| 189 | mediaSrcProp = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 190 | break; | ||
| 191 | case "Name": | ||
| 192 | name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 193 | break; | ||
| 194 | case "SequenceStart": | ||
| 195 | sequenceStart = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); | ||
| 196 | break; | ||
| 197 | case "VolumeLabel": | ||
| 198 | volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 199 | break; | ||
| 200 | default: | ||
| 201 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 202 | break; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | else | ||
| 206 | { | ||
| 207 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | if (null == name) | ||
| 212 | { | ||
| 213 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 214 | } | ||
| 215 | else if (0 < name.Length) | ||
| 216 | { | ||
| 217 | if (8 < name.Length) // check the length | ||
| 218 | { | ||
| 219 | this.Core.Write(ErrorMessages.FamilyNameTooLong(sourceLineNumbers, node.Name.LocalName, "Name", name, name.Length)); | ||
| 220 | } | ||
| 221 | else // check for illegal characters | ||
| 222 | { | ||
| 223 | foreach (var character in name) | ||
| 224 | { | ||
| 225 | if (!Char.IsLetterOrDigit(character) && '_' != character) | ||
| 226 | { | ||
| 227 | this.Core.Write(ErrorMessages.IllegalFamilyName(sourceLineNumbers, node.Name.LocalName, "Name", name)); | ||
| 228 | } | ||
| 229 | } | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | foreach (var child in node.Elements()) | ||
| 234 | { | ||
| 235 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 236 | { | ||
| 237 | switch (child.Name.LocalName) | ||
| 238 | { | ||
| 239 | case "UpgradeImage": | ||
| 240 | this.ParseUpgradeImageElement(child, name); | ||
| 241 | break; | ||
| 242 | case "ExternalFile": | ||
| 243 | this.ParseExternalFileElement(child, name); | ||
| 244 | break; | ||
| 245 | case "ProtectFile": | ||
| 246 | this.ParseProtectFileElement(child, name); | ||
| 247 | break; | ||
| 248 | default: | ||
| 249 | this.Core.UnexpectedElement(node, child); | ||
| 250 | break; | ||
| 251 | } | ||
| 252 | } | ||
| 253 | else | ||
| 254 | { | ||
| 255 | this.Core.ParseExtensionElement(node, child); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | if (!this.Core.EncounteredError) | ||
| 260 | { | ||
| 261 | var symbol = this.Core.AddSymbol(new ImageFamiliesSymbol(sourceLineNumbers) | ||
| 262 | { | ||
| 263 | Family = name, | ||
| 264 | MediaSrcPropName = mediaSrcProp, | ||
| 265 | DiskPrompt = diskPrompt, | ||
| 266 | VolumeLabel = volumeLabel | ||
| 267 | }); | ||
| 268 | |||
| 269 | if (CompilerConstants.IntegerNotSet != diskId) | ||
| 270 | { | ||
| 271 | symbol.MediaDiskId = diskId; | ||
| 272 | } | ||
| 273 | |||
| 274 | if (CompilerConstants.IntegerNotSet != sequenceStart) | ||
| 275 | { | ||
| 276 | symbol.FileSequenceStart = sequenceStart; | ||
| 277 | } | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | /// <summary> | ||
| 282 | /// Parses an upgrade image element. | ||
| 283 | /// </summary> | ||
| 284 | /// <param name="node">The element to parse.</param> | ||
| 285 | /// <param name="family">The family for this element.</param> | ||
| 286 | private void ParseUpgradeImageElement(XElement node, string family) | ||
| 287 | { | ||
| 288 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 289 | string sourceFile = null; | ||
| 290 | string sourcePatch = null; | ||
| 291 | var symbols = new List<string>(); | ||
| 292 | string upgrade = null; | ||
| 293 | |||
| 294 | foreach (var attrib in node.Attributes()) | ||
| 295 | { | ||
| 296 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 297 | { | ||
| 298 | switch (attrib.Name.LocalName) | ||
| 299 | { | ||
| 300 | case "Id": | ||
| 301 | upgrade = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 302 | if (13 < upgrade.Length) | ||
| 303 | { | ||
| 304 | this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", upgrade, 13)); | ||
| 305 | } | ||
| 306 | break; | ||
| 307 | case "SourceFile": | ||
| 308 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 309 | break; | ||
| 310 | case "SourcePatch": | ||
| 311 | sourcePatch = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 312 | break; | ||
| 313 | default: | ||
| 314 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 315 | break; | ||
| 316 | } | ||
| 317 | } | ||
| 318 | else | ||
| 319 | { | ||
| 320 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | |||
| 324 | if (null == upgrade) | ||
| 325 | { | ||
| 326 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 327 | } | ||
| 328 | |||
| 329 | if (null == sourceFile) | ||
| 330 | { | ||
| 331 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
| 332 | } | ||
| 333 | |||
| 334 | foreach (var child in node.Elements()) | ||
| 335 | { | ||
| 336 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 337 | { | ||
| 338 | switch (child.Name.LocalName) | ||
| 339 | { | ||
| 340 | case "SymbolPath": | ||
| 341 | symbols.Add(this.ParseSymbolPathElement(child)); | ||
| 342 | break; | ||
| 343 | case "TargetImage": | ||
| 344 | this.ParseTargetImageElement(child, upgrade, family); | ||
| 345 | break; | ||
| 346 | case "UpgradeFile": | ||
| 347 | this.ParseUpgradeFileElement(child, upgrade); | ||
| 348 | break; | ||
| 349 | default: | ||
| 350 | this.Core.UnexpectedElement(node, child); | ||
| 351 | break; | ||
| 352 | } | ||
| 353 | } | ||
| 354 | else | ||
| 355 | { | ||
| 356 | this.Core.ParseExtensionElement(node, child); | ||
| 357 | } | ||
| 358 | } | ||
| 359 | |||
| 360 | if (!this.Core.EncounteredError) | ||
| 361 | { | ||
| 362 | this.Core.AddSymbol(new UpgradedImagesSymbol(sourceLineNumbers) | ||
| 363 | { | ||
| 364 | Upgraded = upgrade, | ||
| 365 | MsiPath = sourceFile, | ||
| 366 | PatchMsiPath = sourcePatch, | ||
| 367 | SymbolPaths = String.Join(";", symbols), | ||
| 368 | Family = family | ||
| 369 | }); | ||
| 370 | } | ||
| 371 | } | ||
| 372 | |||
| 373 | /// <summary> | ||
| 374 | /// Parses an upgrade file element. | ||
| 375 | /// </summary> | ||
| 376 | /// <param name="node">The element to parse.</param> | ||
| 377 | /// <param name="upgrade">The upgrade key for this element.</param> | ||
| 378 | private void ParseUpgradeFileElement(XElement node, string upgrade) | ||
| 379 | { | ||
| 380 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 381 | var allowIgnoreOnError = false; | ||
| 382 | string file = null; | ||
| 383 | var ignore = false; | ||
| 384 | var symbols = new List<string>(); | ||
| 385 | var wholeFile = false; | ||
| 386 | |||
| 387 | foreach (var attrib in node.Attributes()) | ||
| 388 | { | ||
| 389 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 390 | { | ||
| 391 | switch (attrib.Name.LocalName) | ||
| 392 | { | ||
| 393 | case "AllowIgnoreOnError": | ||
| 394 | allowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 395 | break; | ||
| 396 | case "File": | ||
| 397 | file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 398 | break; | ||
| 399 | case "Ignore": | ||
| 400 | ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 401 | break; | ||
| 402 | case "WholeFile": | ||
| 403 | wholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 404 | break; | ||
| 405 | default: | ||
| 406 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 407 | break; | ||
| 408 | } | ||
| 409 | } | ||
| 410 | else | ||
| 411 | { | ||
| 412 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 413 | } | ||
| 414 | } | ||
| 415 | |||
| 416 | if (null == file) | ||
| 417 | { | ||
| 418 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); | ||
| 419 | } | ||
| 420 | |||
| 421 | foreach (var child in node.Elements()) | ||
| 422 | { | ||
| 423 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 424 | { | ||
| 425 | switch (child.Name.LocalName) | ||
| 426 | { | ||
| 427 | case "SymbolPath": | ||
| 428 | symbols.Add(this.ParseSymbolPathElement(child)); | ||
| 429 | break; | ||
| 430 | default: | ||
| 431 | this.Core.UnexpectedElement(node, child); | ||
| 432 | break; | ||
| 433 | } | ||
| 434 | } | ||
| 435 | else | ||
| 436 | { | ||
| 437 | this.Core.ParseExtensionElement(node, child); | ||
| 438 | } | ||
| 439 | } | ||
| 440 | |||
| 441 | if (!this.Core.EncounteredError) | ||
| 442 | { | ||
| 443 | if (ignore) | ||
| 444 | { | ||
| 445 | this.Core.AddSymbol(new UpgradedFilesToIgnoreSymbol(sourceLineNumbers) | ||
| 446 | { | ||
| 447 | Upgraded = upgrade, | ||
| 448 | FTK = file | ||
| 449 | }); | ||
| 450 | } | ||
| 451 | else | ||
| 452 | { | ||
| 453 | this.Core.AddSymbol(new UpgradedFilesOptionalDataSymbol(sourceLineNumbers) | ||
| 454 | { | ||
| 455 | Upgraded = upgrade, | ||
| 456 | FTK = file, | ||
| 457 | SymbolPaths = String.Join(";", symbols), | ||
| 458 | AllowIgnoreOnPatchError = allowIgnoreOnError, | ||
| 459 | IncludeWholeFile = wholeFile | ||
| 460 | }); | ||
| 461 | } | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | /// <summary> | ||
| 466 | /// Parses a target image element. | ||
| 467 | /// </summary> | ||
| 468 | /// <param name="node">The element to parse.</param> | ||
| 469 | /// <param name="upgrade">The upgrade key for this element.</param> | ||
| 470 | /// <param name="family">The family key for this element.</param> | ||
| 471 | private void ParseTargetImageElement(XElement node, string upgrade, string family) | ||
| 472 | { | ||
| 473 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 474 | var ignore = false; | ||
| 475 | var order = CompilerConstants.IntegerNotSet; | ||
| 476 | string sourceFile = null; | ||
| 477 | string symbols = null; | ||
| 478 | string target = null; | ||
| 479 | string validation = null; | ||
| 480 | |||
| 481 | foreach (var attrib in node.Attributes()) | ||
| 482 | { | ||
| 483 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 484 | { | ||
| 485 | switch (attrib.Name.LocalName) | ||
| 486 | { | ||
| 487 | case "Id": | ||
| 488 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 489 | if (target.Length > 13) | ||
| 490 | { | ||
| 491 | this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", target, 13)); | ||
| 492 | } | ||
| 493 | break; | ||
| 494 | case "IgnoreMissingFiles": | ||
| 495 | ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 496 | break; | ||
| 497 | case "Order": | ||
| 498 | order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); | ||
| 499 | break; | ||
| 500 | case "SourceFile": | ||
| 501 | sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 502 | break; | ||
| 503 | case "Validation": | ||
| 504 | validation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 505 | break; | ||
| 506 | default: | ||
| 507 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 508 | break; | ||
| 509 | } | ||
| 510 | } | ||
| 511 | else | ||
| 512 | { | ||
| 513 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 514 | } | ||
| 515 | } | ||
| 516 | |||
| 517 | if (null == target) | ||
| 518 | { | ||
| 519 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 520 | } | ||
| 521 | |||
| 522 | if (null == sourceFile) | ||
| 523 | { | ||
| 524 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); | ||
| 525 | } | ||
| 526 | |||
| 527 | if (CompilerConstants.IntegerNotSet == order) | ||
| 528 | { | ||
| 529 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); | ||
| 530 | } | ||
| 531 | |||
| 532 | foreach (var child in node.Elements()) | ||
| 533 | { | ||
| 534 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 535 | { | ||
| 536 | switch (child.Name.LocalName) | ||
| 537 | { | ||
| 538 | case "SymbolPath": | ||
| 539 | if (null != symbols) | ||
| 540 | { | ||
| 541 | symbols = String.Concat(symbols, ";", this.ParseSymbolPathElement(child)); | ||
| 542 | } | ||
| 543 | else | ||
| 544 | { | ||
| 545 | symbols = this.ParseSymbolPathElement(child); | ||
| 546 | } | ||
| 547 | break; | ||
| 548 | case "TargetFile": | ||
| 549 | this.ParseTargetFileElement(child, target, family); | ||
| 550 | break; | ||
| 551 | default: | ||
| 552 | this.Core.UnexpectedElement(node, child); | ||
| 553 | break; | ||
| 554 | } | ||
| 555 | } | ||
| 556 | else | ||
| 557 | { | ||
| 558 | this.Core.ParseExtensionElement(node, child); | ||
| 559 | } | ||
| 560 | } | ||
| 561 | |||
| 562 | if (!this.Core.EncounteredError) | ||
| 563 | { | ||
| 564 | this.Core.AddSymbol(new TargetImagesSymbol(sourceLineNumbers) | ||
| 565 | { | ||
| 566 | Target = target, | ||
| 567 | MsiPath = sourceFile, | ||
| 568 | SymbolPaths = symbols, | ||
| 569 | Upgraded = upgrade, | ||
| 570 | Order = order, | ||
| 571 | ProductValidateFlags = validation, | ||
| 572 | IgnoreMissingSrcFiles = ignore | ||
| 573 | }); | ||
| 574 | } | ||
| 575 | } | ||
| 576 | |||
| 577 | /// <summary> | ||
| 578 | /// Parses an upgrade file element. | ||
| 579 | /// </summary> | ||
| 580 | /// <param name="node">The element to parse.</param> | ||
| 581 | /// <param name="target">The upgrade key for this element.</param> | ||
| 582 | /// <param name="family">The family key for this element.</param> | ||
| 583 | private void ParseTargetFileElement(XElement node, string target, string family) | ||
| 584 | { | ||
| 585 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 586 | string file = null; | ||
| 587 | string ignoreLengths = null; | ||
| 588 | string ignoreOffsets = null; | ||
| 589 | string protectLengths = null; | ||
| 590 | string protectOffsets = null; | ||
| 591 | string symbols = null; | ||
| 592 | |||
| 593 | foreach (var attrib in node.Attributes()) | ||
| 594 | { | ||
| 595 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 596 | { | ||
| 597 | switch (attrib.Name.LocalName) | ||
| 598 | { | ||
| 599 | case "Id": | ||
| 600 | file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 601 | break; | ||
| 602 | default: | ||
| 603 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 604 | break; | ||
| 605 | } | ||
| 606 | } | ||
| 607 | else | ||
| 608 | { | ||
| 609 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 610 | } | ||
| 611 | } | ||
| 612 | |||
| 613 | if (null == file) | ||
| 614 | { | ||
| 615 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 616 | } | ||
| 617 | |||
| 618 | foreach (var child in node.Elements()) | ||
| 619 | { | ||
| 620 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 621 | { | ||
| 622 | switch (child.Name.LocalName) | ||
| 623 | { | ||
| 624 | case "IgnoreRange": | ||
| 625 | this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); | ||
| 626 | break; | ||
| 627 | case "ProtectRange": | ||
| 628 | this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); | ||
| 629 | break; | ||
| 630 | case "SymbolPath": | ||
| 631 | symbols = this.ParseSymbolPathElement(child); | ||
| 632 | break; | ||
| 633 | default: | ||
| 634 | this.Core.UnexpectedElement(node, child); | ||
| 635 | break; | ||
| 636 | } | ||
| 637 | } | ||
| 638 | else | ||
| 639 | { | ||
| 640 | this.Core.ParseExtensionElement(node, child); | ||
| 641 | } | ||
| 642 | } | ||
| 643 | |||
| 644 | if (!this.Core.EncounteredError) | ||
| 645 | { | ||
| 646 | var symbol = this.Core.AddSymbol(new TargetFilesOptionalDataSymbol(sourceLineNumbers) | ||
| 647 | { | ||
| 648 | Target = target, | ||
| 649 | FTK = file, | ||
| 650 | SymbolPaths = symbols, | ||
| 651 | IgnoreOffsets = ignoreOffsets, | ||
| 652 | IgnoreLengths = ignoreLengths, | ||
| 653 | }); | ||
| 654 | |||
| 655 | if (null != protectOffsets) | ||
| 656 | { | ||
| 657 | symbol.RetainOffsets = protectOffsets; | ||
| 658 | |||
| 659 | this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) | ||
| 660 | { | ||
| 661 | Family = family, | ||
| 662 | FTK = file, | ||
| 663 | RetainOffsets = protectOffsets, | ||
| 664 | RetainLengths = protectLengths, | ||
| 665 | }); | ||
| 666 | } | ||
| 667 | } | ||
| 668 | } | ||
| 669 | |||
| 670 | /// <summary> | ||
| 671 | /// Parses an external file element. | ||
| 672 | /// </summary> | ||
| 673 | /// <param name="node">The element to parse.</param> | ||
| 674 | /// <param name="family">The family for this element.</param> | ||
| 675 | private void ParseExternalFileElement(XElement node, string family) | ||
| 676 | { | ||
| 677 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 678 | string file = null; | ||
| 679 | string ignoreLengths = null; | ||
| 680 | string ignoreOffsets = null; | ||
| 681 | var order = CompilerConstants.IntegerNotSet; | ||
| 682 | string protectLengths = null; | ||
| 683 | string protectOffsets = null; | ||
| 684 | string source = null; | ||
| 685 | string symbols = null; | ||
| 686 | |||
| 687 | foreach (var attrib in node.Attributes()) | ||
| 688 | { | ||
| 689 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 690 | { | ||
| 691 | switch (attrib.Name.LocalName) | ||
| 692 | { | ||
| 693 | case "File": | ||
| 694 | file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 695 | break; | ||
| 696 | case "Order": | ||
| 697 | order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); | ||
| 698 | break; | ||
| 699 | case "Source": | ||
| 700 | source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 701 | break; | ||
| 702 | default: | ||
| 703 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 704 | break; | ||
| 705 | } | ||
| 706 | } | ||
| 707 | else | ||
| 708 | { | ||
| 709 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 710 | } | ||
| 711 | } | ||
| 712 | |||
| 713 | if (null == file) | ||
| 714 | { | ||
| 715 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); | ||
| 716 | } | ||
| 717 | |||
| 718 | if (null == source) | ||
| 719 | { | ||
| 720 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Source")); | ||
| 721 | } | ||
| 722 | |||
| 723 | if (CompilerConstants.IntegerNotSet == order) | ||
| 724 | { | ||
| 725 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); | ||
| 726 | } | ||
| 727 | |||
| 728 | foreach (var child in node.Elements()) | ||
| 729 | { | ||
| 730 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 731 | { | ||
| 732 | switch (child.Name.LocalName) | ||
| 733 | { | ||
| 734 | case "IgnoreRange": | ||
| 735 | this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); | ||
| 736 | break; | ||
| 737 | case "ProtectRange": | ||
| 738 | this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); | ||
| 739 | break; | ||
| 740 | case "SymbolPath": | ||
| 741 | symbols = this.ParseSymbolPathElement(child); | ||
| 742 | break; | ||
| 743 | default: | ||
| 744 | this.Core.UnexpectedElement(node, child); | ||
| 745 | break; | ||
| 746 | } | ||
| 747 | } | ||
| 748 | else | ||
| 749 | { | ||
| 750 | this.Core.ParseExtensionElement(node, child); | ||
| 751 | } | ||
| 752 | } | ||
| 753 | |||
| 754 | if (!this.Core.EncounteredError) | ||
| 755 | { | ||
| 756 | var symbol = this.Core.AddSymbol(new ExternalFilesSymbol(sourceLineNumbers) | ||
| 757 | { | ||
| 758 | Family = family, | ||
| 759 | FTK = file, | ||
| 760 | FilePath = source, | ||
| 761 | SymbolPaths = symbols, | ||
| 762 | IgnoreOffsets = ignoreOffsets, | ||
| 763 | IgnoreLengths = ignoreLengths, | ||
| 764 | }); | ||
| 765 | |||
| 766 | if (null != protectOffsets) | ||
| 767 | { | ||
| 768 | symbol.RetainOffsets = protectOffsets; | ||
| 769 | } | ||
| 770 | |||
| 771 | if (CompilerConstants.IntegerNotSet != order) | ||
| 772 | { | ||
| 773 | symbol.Order = order; | ||
| 774 | } | ||
| 775 | |||
| 776 | if (null != protectOffsets) | ||
| 777 | { | ||
| 778 | this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) | ||
| 779 | { | ||
| 780 | Family = family, | ||
| 781 | FTK = file, | ||
| 782 | RetainOffsets = protectOffsets, | ||
| 783 | RetainLengths = protectLengths, | ||
| 784 | }); | ||
| 785 | } | ||
| 786 | } | ||
| 787 | } | ||
| 788 | |||
| 789 | /// <summary> | ||
| 790 | /// Parses a protect file element. | ||
| 791 | /// </summary> | ||
| 792 | /// <param name="node">The element to parse.</param> | ||
| 793 | /// <param name="family">The family for this element.</param> | ||
| 794 | private void ParseProtectFileElement(XElement node, string family) | ||
| 795 | { | ||
| 796 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 797 | string file = null; | ||
| 798 | string protectLengths = null; | ||
| 799 | string protectOffsets = null; | ||
| 800 | |||
| 801 | foreach (var attrib in node.Attributes()) | ||
| 802 | { | ||
| 803 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 804 | { | ||
| 805 | switch (attrib.Name.LocalName) | ||
| 806 | { | ||
| 807 | case "File": | ||
| 808 | file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 809 | break; | ||
| 810 | default: | ||
| 811 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 812 | break; | ||
| 813 | } | ||
| 814 | } | ||
| 815 | else | ||
| 816 | { | ||
| 817 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 818 | } | ||
| 819 | } | ||
| 820 | |||
| 821 | if (null == file) | ||
| 822 | { | ||
| 823 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); | ||
| 824 | } | ||
| 825 | |||
| 826 | foreach (var child in node.Elements()) | ||
| 827 | { | ||
| 828 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 829 | { | ||
| 830 | switch (child.Name.LocalName) | ||
| 831 | { | ||
| 832 | case "ProtectRange": | ||
| 833 | this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); | ||
| 834 | break; | ||
| 835 | default: | ||
| 836 | this.Core.UnexpectedElement(node, child); | ||
| 837 | break; | ||
| 838 | } | ||
| 839 | } | ||
| 840 | else | ||
| 841 | { | ||
| 842 | this.Core.ParseExtensionElement(node, child); | ||
| 843 | } | ||
| 844 | } | ||
| 845 | |||
| 846 | if (null == protectOffsets || null == protectLengths) | ||
| 847 | { | ||
| 848 | this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "ProtectRange")); | ||
| 849 | } | ||
| 850 | |||
| 851 | if (!this.Core.EncounteredError) | ||
| 852 | { | ||
| 853 | this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) | ||
| 854 | { | ||
| 855 | Family = family, | ||
| 856 | FTK = file, | ||
| 857 | RetainOffsets = protectOffsets, | ||
| 858 | RetainLengths = protectLengths | ||
| 859 | }); | ||
| 860 | } | ||
| 861 | } | ||
| 862 | |||
| 863 | /// <summary> | ||
| 864 | /// Parses a range element (ProtectRange, IgnoreRange, etc). | ||
| 865 | /// </summary> | ||
| 866 | /// <param name="node">The element to parse.</param> | ||
| 867 | /// <param name="offsets">Reference to the offsets string.</param> | ||
| 868 | /// <param name="lengths">Reference to the lengths string.</param> | ||
| 869 | private void ParseRangeElement(XElement node, ref string offsets, ref string lengths) | ||
| 870 | { | ||
| 871 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 872 | string length = null; | ||
| 873 | string offset = null; | ||
| 874 | |||
| 875 | foreach (var attrib in node.Attributes()) | ||
| 876 | { | ||
| 877 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 878 | { | ||
| 879 | switch (attrib.Name.LocalName) | ||
| 880 | { | ||
| 881 | case "Length": | ||
| 882 | length = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 883 | break; | ||
| 884 | case "Offset": | ||
| 885 | offset = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 886 | break; | ||
| 887 | default: | ||
| 888 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 889 | break; | ||
| 890 | } | ||
| 891 | } | ||
| 892 | else | ||
| 893 | { | ||
| 894 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 895 | } | ||
| 896 | } | ||
| 897 | |||
| 898 | if (null == length) | ||
| 899 | { | ||
| 900 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Length")); | ||
| 901 | } | ||
| 902 | |||
| 903 | if (null == offset) | ||
| 904 | { | ||
| 905 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); | ||
| 906 | } | ||
| 907 | |||
| 908 | this.Core.ParseForExtensionElements(node); | ||
| 909 | |||
| 910 | if (null != lengths) | ||
| 911 | { | ||
| 912 | lengths = String.Concat(lengths, ",", length); | ||
| 913 | } | ||
| 914 | else | ||
| 915 | { | ||
| 916 | lengths = length; | ||
| 917 | } | ||
| 918 | |||
| 919 | if (null != offsets) | ||
| 920 | { | ||
| 921 | offsets = String.Concat(offsets, ",", offset); | ||
| 922 | } | ||
| 923 | else | ||
| 924 | { | ||
| 925 | offsets = offset; | ||
| 926 | } | ||
| 927 | } | ||
| 928 | |||
| 929 | /// <summary> | ||
| 930 | /// Parses a patch metadata element. | ||
| 931 | /// </summary> | ||
| 932 | /// <param name="node">Element to parse.</param> | ||
| 933 | private void ParsePatchMetadataElement(XElement node) | ||
| 934 | { | ||
| 935 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 936 | var allowRemoval = YesNoType.NotSet; | ||
| 937 | string classification = null; | ||
| 938 | string creationTimeUtc = null; | ||
| 939 | string description = null; | ||
| 940 | string displayName = null; | ||
| 941 | string manufacturerName = null; | ||
| 942 | string minorUpdateTargetRTM = null; | ||
| 943 | string moreInfoUrl = null; | ||
| 944 | var optimizeCA = CompilerConstants.IntegerNotSet; | ||
| 945 | var optimizedInstallMode = YesNoType.NotSet; | ||
| 946 | string targetProductName = null; | ||
| 947 | |||
| 948 | foreach (var attrib in node.Attributes()) | ||
| 949 | { | ||
| 950 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 951 | { | ||
| 952 | switch (attrib.Name.LocalName) | ||
| 953 | { | ||
| 954 | case "AllowRemoval": | ||
| 955 | allowRemoval = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 956 | break; | ||
| 957 | case "Classification": | ||
| 958 | classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 959 | break; | ||
| 960 | case "CreationTimeUTC": | ||
| 961 | creationTimeUtc = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 962 | break; | ||
| 963 | case "Description": | ||
| 964 | description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 965 | break; | ||
| 966 | case "DisplayName": | ||
| 967 | displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 968 | break; | ||
| 969 | case "ManufacturerName": | ||
| 970 | manufacturerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 971 | break; | ||
| 972 | case "MinorUpdateTargetRTM": | ||
| 973 | minorUpdateTargetRTM = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 974 | break; | ||
| 975 | case "MoreInfoURL": | ||
| 976 | moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 977 | break; | ||
| 978 | case "OptimizedInstallMode": | ||
| 979 | optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 980 | break; | ||
| 981 | case "TargetProductName": | ||
| 982 | targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 983 | break; | ||
| 984 | default: | ||
| 985 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 986 | break; | ||
| 987 | } | ||
| 988 | } | ||
| 989 | else | ||
| 990 | { | ||
| 991 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 992 | } | ||
| 993 | } | ||
| 994 | |||
| 995 | if (YesNoType.NotSet == allowRemoval) | ||
| 996 | { | ||
| 997 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AllowRemoval")); | ||
| 998 | } | ||
| 999 | |||
| 1000 | if (null == classification) | ||
| 1001 | { | ||
| 1002 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | if (null == description) | ||
| 1006 | { | ||
| 1007 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | if (null == displayName) | ||
| 1011 | { | ||
| 1012 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); | ||
| 1013 | } | ||
| 1014 | |||
| 1015 | if (null == manufacturerName) | ||
| 1016 | { | ||
| 1017 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ManufacturerName")); | ||
| 1018 | } | ||
| 1019 | |||
| 1020 | if (null == moreInfoUrl) | ||
| 1021 | { | ||
| 1022 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MoreInfoURL")); | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | if (null == targetProductName) | ||
| 1026 | { | ||
| 1027 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "TargetProductName")); | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | foreach (var child in node.Elements()) | ||
| 1031 | { | ||
| 1032 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1033 | { | ||
| 1034 | switch (child.Name.LocalName) | ||
| 1035 | { | ||
| 1036 | case "CustomProperty": | ||
| 1037 | this.ParseCustomPropertyElement(child); | ||
| 1038 | break; | ||
| 1039 | case "OptimizeCustomActions": | ||
| 1040 | optimizeCA = this.ParseOptimizeCustomActionsElement(child); | ||
| 1041 | break; | ||
| 1042 | default: | ||
| 1043 | this.Core.UnexpectedElement(node, child); | ||
| 1044 | break; | ||
| 1045 | } | ||
| 1046 | } | ||
| 1047 | else | ||
| 1048 | { | ||
| 1049 | this.Core.ParseExtensionElement(node, child); | ||
| 1050 | } | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | if (!this.Core.EncounteredError) | ||
| 1054 | { | ||
| 1055 | if (YesNoType.NotSet != allowRemoval) | ||
| 1056 | { | ||
| 1057 | this.AddPatchMetadata(sourceLineNumbers, null, "AllowRemoval", YesNoType.Yes == allowRemoval ? "1" : "0"); | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | if (null != classification) | ||
| 1061 | { | ||
| 1062 | this.AddPatchMetadata(sourceLineNumbers, null, "Classification", classification); | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | if (null != creationTimeUtc) | ||
| 1066 | { | ||
| 1067 | this.AddPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", creationTimeUtc); | ||
| 1068 | } | ||
| 1069 | |||
| 1070 | if (null != description) | ||
| 1071 | { | ||
| 1072 | this.AddPatchMetadata(sourceLineNumbers, null, "Description", description); | ||
| 1073 | } | ||
| 1074 | |||
| 1075 | if (null != displayName) | ||
| 1076 | { | ||
| 1077 | this.AddPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); | ||
| 1078 | } | ||
| 1079 | |||
| 1080 | if (null != manufacturerName) | ||
| 1081 | { | ||
| 1082 | this.AddPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturerName); | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | if (null != minorUpdateTargetRTM) | ||
| 1086 | { | ||
| 1087 | this.AddPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", minorUpdateTargetRTM); | ||
| 1088 | } | ||
| 1089 | |||
| 1090 | if (null != moreInfoUrl) | ||
| 1091 | { | ||
| 1092 | this.AddPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); | ||
| 1093 | } | ||
| 1094 | |||
| 1095 | if (CompilerConstants.IntegerNotSet != optimizeCA) | ||
| 1096 | { | ||
| 1097 | this.AddPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | if (YesNoType.NotSet != optimizedInstallMode) | ||
| 1101 | { | ||
| 1102 | this.AddPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); | ||
| 1103 | } | ||
| 1104 | |||
| 1105 | if (null != targetProductName) | ||
| 1106 | { | ||
| 1107 | this.AddPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); | ||
| 1108 | } | ||
| 1109 | } | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | /// <summary> | ||
| 1113 | /// Parses a custom property element for the PatchMetadata table. | ||
| 1114 | /// </summary> | ||
| 1115 | /// <param name="node">Element to parse.</param> | ||
| 1116 | private void ParseCustomPropertyElement(XElement node) | ||
| 1117 | { | ||
| 1118 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1119 | string company = null; | ||
| 1120 | string property = null; | ||
| 1121 | string value = null; | ||
| 1122 | |||
| 1123 | foreach (var attrib in node.Attributes()) | ||
| 1124 | { | ||
| 1125 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1126 | { | ||
| 1127 | switch (attrib.Name.LocalName) | ||
| 1128 | { | ||
| 1129 | case "Company": | ||
| 1130 | company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1131 | break; | ||
| 1132 | case "Property": | ||
| 1133 | property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1134 | break; | ||
| 1135 | case "Value": | ||
| 1136 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1137 | break; | ||
| 1138 | default: | ||
| 1139 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1140 | break; | ||
| 1141 | } | ||
| 1142 | } | ||
| 1143 | else | ||
| 1144 | { | ||
| 1145 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1146 | } | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | if (null == company) | ||
| 1150 | { | ||
| 1151 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); | ||
| 1152 | } | ||
| 1153 | |||
| 1154 | if (null == property) | ||
| 1155 | { | ||
| 1156 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | if (null == value) | ||
| 1160 | { | ||
| 1161 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 1162 | } | ||
| 1163 | |||
| 1164 | this.Core.ParseForExtensionElements(node); | ||
| 1165 | |||
| 1166 | if (!this.Core.EncounteredError) | ||
| 1167 | { | ||
| 1168 | this.AddPatchMetadata(sourceLineNumbers, company, property, value); | ||
| 1169 | } | ||
| 1170 | } | ||
| 1171 | |||
| 1172 | /// <summary> | ||
| 1173 | /// Parses a patch sequence element. | ||
| 1174 | /// </summary> | ||
| 1175 | /// <param name="node">The element to parse.</param> | ||
| 1176 | private void ParsePatchSequenceElement(XElement node) | ||
| 1177 | { | ||
| 1178 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1179 | string family = null; | ||
| 1180 | string target = null; | ||
| 1181 | string sequence = null; | ||
| 1182 | var attributes = 0; | ||
| 1183 | |||
| 1184 | foreach (var attrib in node.Attributes()) | ||
| 1185 | { | ||
| 1186 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1187 | { | ||
| 1188 | switch (attrib.Name.LocalName) | ||
| 1189 | { | ||
| 1190 | case "PatchFamily": | ||
| 1191 | family = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 1192 | break; | ||
| 1193 | case "ProductCode": | ||
| 1194 | if (null != target) | ||
| 1195 | { | ||
| 1196 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "TargetImage")); | ||
| 1197 | } | ||
| 1198 | target = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); | ||
| 1199 | break; | ||
| 1200 | case "Target": | ||
| 1201 | if (null != target) | ||
| 1202 | { | ||
| 1203 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetImage", "ProductCode")); | ||
| 1204 | } | ||
| 1205 | this.Core.Write(WarningMessages.DeprecatedPatchSequenceTargetAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); | ||
| 1206 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1207 | break; | ||
| 1208 | case "TargetImage": | ||
| 1209 | if (null != target) | ||
| 1210 | { | ||
| 1211 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "ProductCode")); | ||
| 1212 | } | ||
| 1213 | target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1214 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.TargetImages, target); | ||
| 1215 | break; | ||
| 1216 | case "Sequence": | ||
| 1217 | sequence = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); | ||
| 1218 | break; | ||
| 1219 | case "Supersede": | ||
| 1220 | if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
| 1221 | { | ||
| 1222 | attributes |= 0x1; | ||
| 1223 | } | ||
| 1224 | break; | ||
| 1225 | default: | ||
| 1226 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1227 | break; | ||
| 1228 | } | ||
| 1229 | } | ||
| 1230 | else | ||
| 1231 | { | ||
| 1232 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1233 | } | ||
| 1234 | } | ||
| 1235 | |||
| 1236 | if (null == family) | ||
| 1237 | { | ||
| 1238 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "PatchFamily")); | ||
| 1239 | } | ||
| 1240 | |||
| 1241 | this.Core.ParseForExtensionElements(node); | ||
| 1242 | |||
| 1243 | if (!this.Core.EncounteredError) | ||
| 1244 | { | ||
| 1245 | this.Core.AddSymbol(new PatchSequenceSymbol(sourceLineNumbers) | ||
| 1246 | { | ||
| 1247 | PatchFamily = family, | ||
| 1248 | Target = target, | ||
| 1249 | Sequence = sequence, | ||
| 1250 | Supersede = attributes, | ||
| 1251 | }); | ||
| 1252 | } | ||
| 1253 | } | ||
| 1254 | |||
| 1255 | private void AddPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) | ||
| 1256 | { | ||
| 1257 | this.Core.AddSymbol(new PatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) | ||
| 1258 | { | ||
| 1259 | Company = company, | ||
| 1260 | Property = property, | ||
| 1261 | Value = value, | ||
| 1262 | }); | ||
| 1263 | } | ||
| 1264 | } | ||
| 1265 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler_Tag.cs b/src/wix/WixToolset.Core/Compiler_Tag.cs new file mode 100644 index 00000000..cf55c448 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Tag.cs | |||
| @@ -0,0 +1,315 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml.Linq; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Symbols; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Compiler of the WiX toolset. | ||
| 12 | /// </summary> | ||
| 13 | internal partial class Compiler : ICompiler | ||
| 14 | { | ||
| 15 | /// <summary> | ||
| 16 | /// Parses a Tag element for Software Id Tag registration under a Bundle element. | ||
| 17 | /// </summary> | ||
| 18 | /// <param name="node">The element to parse.</param> | ||
| 19 | private void ParseBundleTagElement(XElement node) | ||
| 20 | { | ||
| 21 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 22 | string name = null; | ||
| 23 | string regid = null; | ||
| 24 | string installPath = null; | ||
| 25 | |||
| 26 | foreach (var attrib in node.Attributes()) | ||
| 27 | { | ||
| 28 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 29 | { | ||
| 30 | switch (attrib.Name.LocalName) | ||
| 31 | { | ||
| 32 | case "Name": | ||
| 33 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 34 | break; | ||
| 35 | case "Regid": | ||
| 36 | regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 37 | break; | ||
| 38 | case "InstallDirectory": | ||
| 39 | case "Bitness": | ||
| 40 | this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Package")); | ||
| 41 | break; | ||
| 42 | case "InstallPath": | ||
| 43 | installPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 44 | break; | ||
| 45 | default: | ||
| 46 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 47 | break; | ||
| 48 | } | ||
| 49 | } | ||
| 50 | else | ||
| 51 | { | ||
| 52 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | this.Core.ParseForExtensionElements(node); | ||
| 57 | |||
| 58 | if (String.IsNullOrEmpty(name)) | ||
| 59 | { | ||
| 60 | name = node.Parent?.Attribute("Name")?.Value; | ||
| 61 | |||
| 62 | if (String.IsNullOrEmpty(name)) | ||
| 63 | { | ||
| 64 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) | ||
| 69 | { | ||
| 70 | this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); | ||
| 71 | } | ||
| 72 | |||
| 73 | if (String.IsNullOrEmpty(regid)) | ||
| 74 | { | ||
| 75 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); | ||
| 76 | } | ||
| 77 | else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) | ||
| 78 | { | ||
| 79 | this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); | ||
| 80 | } | ||
| 81 | |||
| 82 | if (String.IsNullOrEmpty(installPath)) | ||
| 83 | { | ||
| 84 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPath")); | ||
| 85 | } | ||
| 86 | |||
| 87 | if (!this.Core.EncounteredError) | ||
| 88 | { | ||
| 89 | this.Core.AddSymbol(new WixBundleTagSymbol(sourceLineNumbers) | ||
| 90 | { | ||
| 91 | Filename = String.Concat(name, ".swidtag"), | ||
| 92 | Regid = regid, | ||
| 93 | Name = name, | ||
| 94 | InstallPath = installPath | ||
| 95 | }); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | /// <summary> | ||
| 100 | /// Parses a Tag element for Software Id Tag registration under a Package element. | ||
| 101 | /// </summary> | ||
| 102 | /// <param name="node">The element to parse.</param> | ||
| 103 | private void ParsePackageTagElement(XElement node) | ||
| 104 | { | ||
| 105 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 106 | Identifier id = null; | ||
| 107 | string name = null; | ||
| 108 | string regid = null; | ||
| 109 | string feature = null; | ||
| 110 | string installDirectory = null; | ||
| 111 | var win64 = this.Context.IsCurrentPlatform64Bit; | ||
| 112 | |||
| 113 | foreach (var attrib in node.Attributes()) | ||
| 114 | { | ||
| 115 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 116 | { | ||
| 117 | switch (attrib.Name.LocalName) | ||
| 118 | { | ||
| 119 | case "Id": | ||
| 120 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 121 | break; | ||
| 122 | case "Name": | ||
| 123 | name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); | ||
| 124 | break; | ||
| 125 | case "Regid": | ||
| 126 | regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 127 | break; | ||
| 128 | case "Feature": | ||
| 129 | feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 130 | break; | ||
| 131 | case "InstallDirectory": | ||
| 132 | installDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 133 | break; | ||
| 134 | case "InstallPath": | ||
| 135 | this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bundle")); | ||
| 136 | break; | ||
| 137 | case "Bitness": | ||
| 138 | var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 139 | switch (bitnessValue) | ||
| 140 | { | ||
| 141 | case "always32": | ||
| 142 | win64 = false; | ||
| 143 | break; | ||
| 144 | case "always64": | ||
| 145 | win64 = true; | ||
| 146 | break; | ||
| 147 | case "default": | ||
| 148 | case "": | ||
| 149 | break; | ||
| 150 | default: | ||
| 151 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); | ||
| 152 | break; | ||
| 153 | } | ||
| 154 | break; | ||
| 155 | default: | ||
| 156 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 157 | break; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | else | ||
| 161 | { | ||
| 162 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | this.Core.ParseForExtensionElements(node); | ||
| 167 | |||
| 168 | if (String.IsNullOrEmpty(name)) | ||
| 169 | { | ||
| 170 | name = node.Parent?.Attribute("Name")?.Value; | ||
| 171 | |||
| 172 | if (String.IsNullOrEmpty(name)) | ||
| 173 | { | ||
| 174 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) | ||
| 179 | { | ||
| 180 | this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); | ||
| 181 | } | ||
| 182 | |||
| 183 | if (String.IsNullOrEmpty(regid)) | ||
| 184 | { | ||
| 185 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); | ||
| 186 | } | ||
| 187 | else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) | ||
| 188 | { | ||
| 189 | this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); | ||
| 190 | return; | ||
| 191 | } | ||
| 192 | else if (id == null) | ||
| 193 | { | ||
| 194 | id = this.CreateTagId(regid); | ||
| 195 | } | ||
| 196 | |||
| 197 | if (String.IsNullOrEmpty(installDirectory)) | ||
| 198 | { | ||
| 199 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallDirectory")); | ||
| 200 | } | ||
| 201 | |||
| 202 | if (!this.Core.EncounteredError) | ||
| 203 | { | ||
| 204 | var fileName = String.Concat(name, ".swidtag"); | ||
| 205 | |||
| 206 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, installDirectory); | ||
| 207 | this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) | ||
| 208 | { | ||
| 209 | Name = "swidtag", | ||
| 210 | ParentDirectoryRef = installDirectory, | ||
| 211 | ComponentGuidGenerationSeed = "4BAD0C8B-3AF0-BFE3-CC83-094749A1C4B1" | ||
| 212 | }); | ||
| 213 | |||
| 214 | this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) | ||
| 215 | { | ||
| 216 | ComponentId = "*", | ||
| 217 | DirectoryRef = id.Id, | ||
| 218 | KeyPath = id.Id, | ||
| 219 | KeyPathType = ComponentKeyPathType.File, | ||
| 220 | Location = ComponentLocation.LocalOnly, | ||
| 221 | Win64 = win64 | ||
| 222 | }); | ||
| 223 | |||
| 224 | this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) | ||
| 225 | { | ||
| 226 | ComponentRef = id.Id, | ||
| 227 | Name = fileName, | ||
| 228 | DiskId = 1, | ||
| 229 | Attributes = FileSymbolAttributes.ReadOnly, | ||
| 230 | }); | ||
| 231 | |||
| 232 | if (!String.IsNullOrEmpty(feature)) | ||
| 233 | { | ||
| 234 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); | ||
| 235 | } | ||
| 236 | else | ||
| 237 | { | ||
| 238 | feature = "WixSwidTag"; | ||
| 239 | this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, feature)) | ||
| 240 | { | ||
| 241 | Title = "ISO/IEC 19770-2", | ||
| 242 | Level = 1, | ||
| 243 | InstallDefault = FeatureInstallDefault.Local, | ||
| 244 | Display = 0, | ||
| 245 | DisallowAdvertise = true, | ||
| 246 | DisallowAbsent = true, | ||
| 247 | }); | ||
| 248 | } | ||
| 249 | this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); | ||
| 250 | |||
| 251 | this.Core.EnsureTable(sourceLineNumbers, "SoftwareIdentificationTag"); | ||
| 252 | this.Core.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, id) | ||
| 253 | { | ||
| 254 | FileRef = id.Id, | ||
| 255 | Regid = regid, | ||
| 256 | Name = name | ||
| 257 | }); | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | /// <summary> | ||
| 262 | /// Parses a TagRef element for Software Id Tag registration under a PatchFamily element. | ||
| 263 | /// </summary> | ||
| 264 | /// <param name="node">The element to parse.</param> | ||
| 265 | private void ParseTagRefElement(XElement node) | ||
| 266 | { | ||
| 267 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 268 | string regid = null; | ||
| 269 | |||
| 270 | foreach (var attrib in node.Attributes()) | ||
| 271 | { | ||
| 272 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 273 | { | ||
| 274 | switch (attrib.Name.LocalName) | ||
| 275 | { | ||
| 276 | case "Regid": | ||
| 277 | regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 278 | break; | ||
| 279 | default: | ||
| 280 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 281 | break; | ||
| 282 | } | ||
| 283 | } | ||
| 284 | else | ||
| 285 | { | ||
| 286 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | this.Core.ParseForExtensionElements(node); | ||
| 291 | |||
| 292 | if (String.IsNullOrEmpty(regid)) | ||
| 293 | { | ||
| 294 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); | ||
| 295 | } | ||
| 296 | else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) | ||
| 297 | { | ||
| 298 | this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); | ||
| 299 | } | ||
| 300 | |||
| 301 | if (!this.Core.EncounteredError) | ||
| 302 | { | ||
| 303 | var id = this.CreateTagId(regid); | ||
| 304 | |||
| 305 | this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers, id) | ||
| 306 | { | ||
| 307 | Table = SymbolDefinitions.Component.Name, | ||
| 308 | PrimaryKeys = id.Id | ||
| 309 | }); | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | private Identifier CreateTagId(string regid) => this.Core.CreateIdentifier("tag", regid, ".product.tag"); | ||
| 314 | } | ||
| 315 | } | ||
diff --git a/src/wix/WixToolset.Core/Compiler_UI.cs b/src/wix/WixToolset.Core/Compiler_UI.cs new file mode 100644 index 00000000..d712ec91 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_UI.cs | |||
| @@ -0,0 +1,1808 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Xml.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Data.WindowsInstaller; | ||
| 11 | using WixToolset.Extensibility; | ||
| 12 | |||
| 13 | /// <summary> | ||
| 14 | /// Compiler of the WiX toolset. | ||
| 15 | /// </summary> | ||
| 16 | internal partial class Compiler : ICompiler | ||
| 17 | { | ||
| 18 | // NameToBit arrays | ||
| 19 | private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; | ||
| 20 | private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; | ||
| 21 | private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; | ||
| 22 | private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; | ||
| 23 | private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; | ||
| 24 | private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; | ||
| 25 | private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; | ||
| 26 | private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; | ||
| 27 | private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; | ||
| 28 | private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; | ||
| 29 | private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; | ||
| 30 | private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; | ||
| 31 | private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; | ||
| 32 | |||
| 33 | /// <summary> | ||
| 34 | /// Parses UI elements. | ||
| 35 | /// </summary> | ||
| 36 | /// <param name="node">Element to parse.</param> | ||
| 37 | private void ParseUIElement(XElement node) | ||
| 38 | { | ||
| 39 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 40 | Identifier id = null; | ||
| 41 | var embeddedUICount = 0; | ||
| 42 | |||
| 43 | foreach (var attrib in node.Attributes()) | ||
| 44 | { | ||
| 45 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 46 | { | ||
| 47 | switch (attrib.Name.LocalName) | ||
| 48 | { | ||
| 49 | case "Id": | ||
| 50 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 51 | break; | ||
| 52 | default: | ||
| 53 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 54 | break; | ||
| 55 | } | ||
| 56 | } | ||
| 57 | else | ||
| 58 | { | ||
| 59 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | foreach (var child in node.Elements()) | ||
| 64 | { | ||
| 65 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 66 | { | ||
| 67 | switch (child.Name.LocalName) | ||
| 68 | { | ||
| 69 | case "BillboardAction": | ||
| 70 | this.ParseBillboardActionElement(child); | ||
| 71 | break; | ||
| 72 | case "ComboBox": | ||
| 73 | this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); | ||
| 74 | break; | ||
| 75 | case "Dialog": | ||
| 76 | this.ParseDialogElement(child); | ||
| 77 | break; | ||
| 78 | case "DialogRef": | ||
| 79 | this.ParseSimpleRefElement(child, SymbolDefinitions.Dialog); | ||
| 80 | break; | ||
| 81 | case "EmbeddedUI": | ||
| 82 | if (0 < embeddedUICount) // there can be only one embedded UI | ||
| 83 | { | ||
| 84 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 85 | this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); | ||
| 86 | } | ||
| 87 | this.ParseEmbeddedUIElement(child); | ||
| 88 | ++embeddedUICount; | ||
| 89 | break; | ||
| 90 | case "Error": | ||
| 91 | this.ParseErrorElement(child); | ||
| 92 | break; | ||
| 93 | case "ListBox": | ||
| 94 | this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); | ||
| 95 | break; | ||
| 96 | case "ListView": | ||
| 97 | this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); | ||
| 98 | break; | ||
| 99 | case "ProgressText": | ||
| 100 | this.ParseActionTextElement(child); | ||
| 101 | break; | ||
| 102 | case "Publish": | ||
| 103 | var order = 0; | ||
| 104 | this.ParsePublishElement(child, null, null, ref order); | ||
| 105 | break; | ||
| 106 | case "RadioButtonGroup": | ||
| 107 | var radioButtonType = this.ParseRadioButtonGroupElement(child, null, RadioButtonType.NotSet); | ||
| 108 | if (RadioButtonType.Bitmap == radioButtonType || RadioButtonType.Icon == radioButtonType) | ||
| 109 | { | ||
| 110 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 111 | this.Core.Write(ErrorMessages.RadioButtonBitmapAndIconDisallowed(childSourceLineNumbers)); | ||
| 112 | } | ||
| 113 | break; | ||
| 114 | case "TextStyle": | ||
| 115 | this.ParseTextStyleElement(child); | ||
| 116 | break; | ||
| 117 | case "UIText": | ||
| 118 | this.ParseUITextElement(child); | ||
| 119 | break; | ||
| 120 | |||
| 121 | // the following are available indentically under the UI and Product elements for document organization use only | ||
| 122 | case "AdminUISequence": | ||
| 123 | this.ParseSequenceElement(child, SequenceTable.AdminUISequence); | ||
| 124 | break; | ||
| 125 | case "InstallUISequence": | ||
| 126 | this.ParseSequenceElement(child, SequenceTable.InstallUISequence); | ||
| 127 | break; | ||
| 128 | case "Binary": | ||
| 129 | this.ParseBinaryElement(child); | ||
| 130 | break; | ||
| 131 | case "Property": | ||
| 132 | this.ParsePropertyElement(child); | ||
| 133 | break; | ||
| 134 | case "PropertyRef": | ||
| 135 | this.ParseSimpleRefElement(child, SymbolDefinitions.Property); | ||
| 136 | break; | ||
| 137 | case "UIRef": | ||
| 138 | this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); | ||
| 139 | break; | ||
| 140 | |||
| 141 | default: | ||
| 142 | this.Core.UnexpectedElement(node, child); | ||
| 143 | break; | ||
| 144 | } | ||
| 145 | } | ||
| 146 | else | ||
| 147 | { | ||
| 148 | this.Core.ParseExtensionElement(node, child); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | if (null != id && !this.Core.EncounteredError) | ||
| 153 | { | ||
| 154 | this.Core.AddSymbol(new WixUISymbol(sourceLineNumbers, id)); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | /// <summary> | ||
| 159 | /// Parses a list item element. | ||
| 160 | /// </summary> | ||
| 161 | /// <param name="node">Element to parse.</param> | ||
| 162 | /// <param name="symbolType">Type of symbol to create.</param> | ||
| 163 | /// <param name="property">Identifier of property referred to by list item.</param> | ||
| 164 | /// <param name="order">Relative order of list items.</param> | ||
| 165 | private void ParseListItemElement(XElement node, SymbolDefinitionType symbolType, string property, ref int order) | ||
| 166 | { | ||
| 167 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 168 | string icon = null; | ||
| 169 | string text = null; | ||
| 170 | string value = null; | ||
| 171 | |||
| 172 | foreach (var attrib in node.Attributes()) | ||
| 173 | { | ||
| 174 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 175 | { | ||
| 176 | switch (attrib.Name.LocalName) | ||
| 177 | { | ||
| 178 | case "Icon": | ||
| 179 | if (SymbolDefinitionType.ListView == symbolType) | ||
| 180 | { | ||
| 181 | icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 182 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, icon); | ||
| 183 | } | ||
| 184 | else | ||
| 185 | { | ||
| 186 | this.Core.Write(ErrorMessages.IllegalAttributeExceptOnElement(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ListView")); | ||
| 187 | } | ||
| 188 | break; | ||
| 189 | case "Text": | ||
| 190 | text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 191 | break; | ||
| 192 | case "Value": | ||
| 193 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 194 | break; | ||
| 195 | default: | ||
| 196 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 197 | break; | ||
| 198 | } | ||
| 199 | } | ||
| 200 | else | ||
| 201 | { | ||
| 202 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | if (null == value) | ||
| 207 | { | ||
| 208 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 209 | } | ||
| 210 | |||
| 211 | this.Core.ParseForExtensionElements(node); | ||
| 212 | |||
| 213 | if (!this.Core.EncounteredError) | ||
| 214 | { | ||
| 215 | switch (symbolType) | ||
| 216 | { | ||
| 217 | case SymbolDefinitionType.ComboBox: | ||
| 218 | this.Core.AddSymbol(new ComboBoxSymbol(sourceLineNumbers) | ||
| 219 | { | ||
| 220 | Property = property, | ||
| 221 | Order = ++order, | ||
| 222 | Value = value, | ||
| 223 | Text = text, | ||
| 224 | }); | ||
| 225 | break; | ||
| 226 | case SymbolDefinitionType.ListBox: | ||
| 227 | this.Core.AddSymbol(new ListBoxSymbol(sourceLineNumbers) | ||
| 228 | { | ||
| 229 | Property = property, | ||
| 230 | Order = ++order, | ||
| 231 | Value = value, | ||
| 232 | Text = text, | ||
| 233 | }); | ||
| 234 | break; | ||
| 235 | case SymbolDefinitionType.ListView: | ||
| 236 | var symbol = this.Core.AddSymbol(new ListViewSymbol(sourceLineNumbers) | ||
| 237 | { | ||
| 238 | Property = property, | ||
| 239 | Order = ++order, | ||
| 240 | Value = value, | ||
| 241 | Text = text, | ||
| 242 | }); | ||
| 243 | |||
| 244 | if (null != icon) | ||
| 245 | { | ||
| 246 | symbol.BinaryRef = icon; | ||
| 247 | } | ||
| 248 | break; | ||
| 249 | default: | ||
| 250 | throw new ArgumentOutOfRangeException(nameof(symbolType)); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | /// <summary> | ||
| 256 | /// Parses a radio button element. | ||
| 257 | /// </summary> | ||
| 258 | /// <param name="node">Element to parse.</param> | ||
| 259 | /// <param name="property">Identifier of property referred to by radio button.</param> | ||
| 260 | /// <param name="order">Relative order of radio buttons.</param> | ||
| 261 | /// <returns>Type of this radio button.</returns> | ||
| 262 | private RadioButtonType ParseRadioButtonElement(XElement node, string property, ref int order) | ||
| 263 | { | ||
| 264 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 265 | var type = RadioButtonType.NotSet; | ||
| 266 | string value = null; | ||
| 267 | string x = null; | ||
| 268 | string y = null; | ||
| 269 | string width = null; | ||
| 270 | string height = null; | ||
| 271 | string text = null; | ||
| 272 | string tooltip = null; | ||
| 273 | string help = null; | ||
| 274 | |||
| 275 | foreach (var attrib in node.Attributes()) | ||
| 276 | { | ||
| 277 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 278 | { | ||
| 279 | switch (attrib.Name.LocalName) | ||
| 280 | { | ||
| 281 | case "Bitmap": | ||
| 282 | if (RadioButtonType.NotSet != type) | ||
| 283 | { | ||
| 284 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Icon", "Text")); | ||
| 285 | } | ||
| 286 | text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 287 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); | ||
| 288 | type = RadioButtonType.Bitmap; | ||
| 289 | break; | ||
| 290 | case "Height": | ||
| 291 | height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 292 | break; | ||
| 293 | case "Help": | ||
| 294 | help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 295 | break; | ||
| 296 | case "Icon": | ||
| 297 | if (RadioButtonType.NotSet != type) | ||
| 298 | { | ||
| 299 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Text")); | ||
| 300 | } | ||
| 301 | text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 302 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); | ||
| 303 | type = RadioButtonType.Icon; | ||
| 304 | break; | ||
| 305 | case "Text": | ||
| 306 | if (RadioButtonType.NotSet != type) | ||
| 307 | { | ||
| 308 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Icon")); | ||
| 309 | } | ||
| 310 | text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 311 | type = RadioButtonType.Text; | ||
| 312 | break; | ||
| 313 | case "ToolTip": | ||
| 314 | tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 315 | break; | ||
| 316 | case "Value": | ||
| 317 | value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 318 | break; | ||
| 319 | case "Width": | ||
| 320 | width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 321 | break; | ||
| 322 | case "X": | ||
| 323 | x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 324 | break; | ||
| 325 | case "Y": | ||
| 326 | y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 327 | break; | ||
| 328 | default: | ||
| 329 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 330 | break; | ||
| 331 | } | ||
| 332 | } | ||
| 333 | else | ||
| 334 | { | ||
| 335 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | if (null == value) | ||
| 340 | { | ||
| 341 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); | ||
| 342 | } | ||
| 343 | |||
| 344 | if (null == x) | ||
| 345 | { | ||
| 346 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); | ||
| 347 | } | ||
| 348 | |||
| 349 | if (null == y) | ||
| 350 | { | ||
| 351 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); | ||
| 352 | } | ||
| 353 | |||
| 354 | if (null == width) | ||
| 355 | { | ||
| 356 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); | ||
| 357 | } | ||
| 358 | |||
| 359 | if (null == height) | ||
| 360 | { | ||
| 361 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); | ||
| 362 | } | ||
| 363 | |||
| 364 | this.Core.ParseForExtensionElements(node); | ||
| 365 | |||
| 366 | if (!this.Core.EncounteredError) | ||
| 367 | { | ||
| 368 | var symbol = this.Core.AddSymbol(new RadioButtonSymbol(sourceLineNumbers) | ||
| 369 | { | ||
| 370 | Property = property, | ||
| 371 | Order = ++order, | ||
| 372 | Value = value, | ||
| 373 | Text = text, | ||
| 374 | Help = (null != tooltip || null != help) ? String.Concat(tooltip, "|", help) : null | ||
| 375 | }); | ||
| 376 | |||
| 377 | symbol.Set((int)RadioButtonSymbolFields.X, x); | ||
| 378 | symbol.Set((int)RadioButtonSymbolFields.Y, y); | ||
| 379 | symbol.Set((int)RadioButtonSymbolFields.Width, width); | ||
| 380 | symbol.Set((int)RadioButtonSymbolFields.Height, height); | ||
| 381 | } | ||
| 382 | |||
| 383 | return type; | ||
| 384 | } | ||
| 385 | |||
| 386 | /// <summary> | ||
| 387 | /// Parses a billboard element. | ||
| 388 | /// </summary> | ||
| 389 | /// <param name="node">Element to parse.</param> | ||
| 390 | private void ParseBillboardActionElement(XElement node) | ||
| 391 | { | ||
| 392 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 393 | string action = null; | ||
| 394 | var order = 0; | ||
| 395 | |||
| 396 | foreach (var attrib in node.Attributes()) | ||
| 397 | { | ||
| 398 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 399 | { | ||
| 400 | switch (attrib.Name.LocalName) | ||
| 401 | { | ||
| 402 | case "Id": | ||
| 403 | action = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 404 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", action); | ||
| 405 | break; | ||
| 406 | default: | ||
| 407 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 408 | break; | ||
| 409 | } | ||
| 410 | } | ||
| 411 | else | ||
| 412 | { | ||
| 413 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | |||
| 417 | if (null == action) | ||
| 418 | { | ||
| 419 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 420 | } | ||
| 421 | |||
| 422 | foreach (var child in node.Elements()) | ||
| 423 | { | ||
| 424 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 425 | { | ||
| 426 | switch (child.Name.LocalName) | ||
| 427 | { | ||
| 428 | case "Billboard": | ||
| 429 | order = order + 1; | ||
| 430 | this.ParseBillboardElement(child, action, order); | ||
| 431 | break; | ||
| 432 | default: | ||
| 433 | this.Core.UnexpectedElement(node, child); | ||
| 434 | break; | ||
| 435 | } | ||
| 436 | } | ||
| 437 | else | ||
| 438 | { | ||
| 439 | this.Core.ParseExtensionElement(node, child); | ||
| 440 | } | ||
| 441 | } | ||
| 442 | } | ||
| 443 | |||
| 444 | /// <summary> | ||
| 445 | /// Parses a billboard element. | ||
| 446 | /// </summary> | ||
| 447 | /// <param name="node">Element to parse.</param> | ||
| 448 | /// <param name="action">Action for the billboard.</param> | ||
| 449 | /// <param name="order">Order of the billboard.</param> | ||
| 450 | private void ParseBillboardElement(XElement node, string action, int order) | ||
| 451 | { | ||
| 452 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 453 | Identifier id = null; | ||
| 454 | string feature = null; | ||
| 455 | |||
| 456 | foreach (var attrib in node.Attributes()) | ||
| 457 | { | ||
| 458 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 459 | { | ||
| 460 | switch (attrib.Name.LocalName) | ||
| 461 | { | ||
| 462 | case "Id": | ||
| 463 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 464 | break; | ||
| 465 | case "Feature": | ||
| 466 | feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 467 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); | ||
| 468 | break; | ||
| 469 | default: | ||
| 470 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 471 | break; | ||
| 472 | } | ||
| 473 | } | ||
| 474 | else | ||
| 475 | { | ||
| 476 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 477 | } | ||
| 478 | } | ||
| 479 | |||
| 480 | if (null == id) | ||
| 481 | { | ||
| 482 | id = this.Core.CreateIdentifier("bil", action, order.ToString(), feature); | ||
| 483 | } | ||
| 484 | |||
| 485 | foreach (var child in node.Elements()) | ||
| 486 | { | ||
| 487 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 488 | { | ||
| 489 | switch (child.Name.LocalName) | ||
| 490 | { | ||
| 491 | case "Control": | ||
| 492 | // These are all thrown away. | ||
| 493 | ControlSymbol lastTabSymbol = null; | ||
| 494 | string firstControl = null; | ||
| 495 | string defaultControl = null; | ||
| 496 | string cancelControl = null; | ||
| 497 | |||
| 498 | this.ParseControlElement(child, id.Id, SymbolDefinitionType.BBControl, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); | ||
| 499 | break; | ||
| 500 | default: | ||
| 501 | this.Core.UnexpectedElement(node, child); | ||
| 502 | break; | ||
| 503 | } | ||
| 504 | } | ||
| 505 | else | ||
| 506 | { | ||
| 507 | this.Core.ParseExtensionElement(node, child); | ||
| 508 | } | ||
| 509 | } | ||
| 510 | |||
| 511 | |||
| 512 | if (!this.Core.EncounteredError) | ||
| 513 | { | ||
| 514 | this.Core.AddSymbol(new BillboardSymbol(sourceLineNumbers, id) | ||
| 515 | { | ||
| 516 | FeatureRef = feature, | ||
| 517 | Action = action, | ||
| 518 | Ordering = order | ||
| 519 | }); | ||
| 520 | } | ||
| 521 | } | ||
| 522 | |||
| 523 | /// <summary> | ||
| 524 | /// Parses a control group element. | ||
| 525 | /// </summary> | ||
| 526 | /// <param name="node">Element to parse.</param> | ||
| 527 | /// <param name="symbolType">Symbol type referred to by control group.</param> | ||
| 528 | /// <param name="childTag">Expected child elements.</param> | ||
| 529 | private void ParseControlGroupElement(XElement node, SymbolDefinitionType symbolType, string childTag) | ||
| 530 | { | ||
| 531 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 532 | var order = 0; | ||
| 533 | string property = null; | ||
| 534 | |||
| 535 | foreach (var attrib in node.Attributes()) | ||
| 536 | { | ||
| 537 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 538 | { | ||
| 539 | switch (attrib.Name.LocalName) | ||
| 540 | { | ||
| 541 | case "Property": | ||
| 542 | property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 543 | break; | ||
| 544 | default: | ||
| 545 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 546 | break; | ||
| 547 | } | ||
| 548 | } | ||
| 549 | else | ||
| 550 | { | ||
| 551 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 552 | } | ||
| 553 | } | ||
| 554 | |||
| 555 | if (null == property) | ||
| 556 | { | ||
| 557 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); | ||
| 558 | } | ||
| 559 | |||
| 560 | foreach (var child in node.Elements()) | ||
| 561 | { | ||
| 562 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 563 | { | ||
| 564 | if (childTag != child.Name.LocalName) | ||
| 565 | { | ||
| 566 | this.Core.UnexpectedElement(node, child); | ||
| 567 | } | ||
| 568 | |||
| 569 | switch (child.Name.LocalName) | ||
| 570 | { | ||
| 571 | case "ListItem": | ||
| 572 | this.ParseListItemElement(child, symbolType, property, ref order); | ||
| 573 | break; | ||
| 574 | case "Property": | ||
| 575 | this.ParsePropertyElement(child); | ||
| 576 | break; | ||
| 577 | default: | ||
| 578 | this.Core.UnexpectedElement(node, child); | ||
| 579 | break; | ||
| 580 | } | ||
| 581 | } | ||
| 582 | else | ||
| 583 | { | ||
| 584 | this.Core.ParseExtensionElement(node, child); | ||
| 585 | } | ||
| 586 | } | ||
| 587 | |||
| 588 | } | ||
| 589 | |||
| 590 | /// <summary> | ||
| 591 | /// Parses a radio button control group element. | ||
| 592 | /// </summary> | ||
| 593 | /// <param name="node">Element to parse.</param> | ||
| 594 | /// <param name="property">Property associated with this radio button group.</param> | ||
| 595 | /// <param name="groupType">Specifies the current type of radio buttons in the group.</param> | ||
| 596 | /// <returns>The current type of radio buttons in the group.</returns> | ||
| 597 | private RadioButtonType ParseRadioButtonGroupElement(XElement node, string property, RadioButtonType groupType) | ||
| 598 | { | ||
| 599 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 600 | var order = 0; | ||
| 601 | |||
| 602 | foreach (var attrib in node.Attributes()) | ||
| 603 | { | ||
| 604 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 605 | { | ||
| 606 | switch (attrib.Name.LocalName) | ||
| 607 | { | ||
| 608 | case "Property": | ||
| 609 | property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 610 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); | ||
| 611 | break; | ||
| 612 | default: | ||
| 613 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 614 | break; | ||
| 615 | } | ||
| 616 | } | ||
| 617 | else | ||
| 618 | { | ||
| 619 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 620 | } | ||
| 621 | } | ||
| 622 | |||
| 623 | if (null == property) | ||
| 624 | { | ||
| 625 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); | ||
| 626 | } | ||
| 627 | |||
| 628 | foreach (var child in node.Elements()) | ||
| 629 | { | ||
| 630 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 631 | { | ||
| 632 | switch (child.Name.LocalName) | ||
| 633 | { | ||
| 634 | case "RadioButton": | ||
| 635 | var type = this.ParseRadioButtonElement(child, property, ref order); | ||
| 636 | if (RadioButtonType.NotSet == groupType) | ||
| 637 | { | ||
| 638 | groupType = type; | ||
| 639 | } | ||
| 640 | else if (groupType != type) | ||
| 641 | { | ||
| 642 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 643 | this.Core.Write(ErrorMessages.RadioButtonTypeInconsistent(childSourceLineNumbers)); | ||
| 644 | } | ||
| 645 | break; | ||
| 646 | default: | ||
| 647 | this.Core.UnexpectedElement(node, child); | ||
| 648 | break; | ||
| 649 | } | ||
| 650 | } | ||
| 651 | else | ||
| 652 | { | ||
| 653 | this.Core.ParseExtensionElement(node, child); | ||
| 654 | } | ||
| 655 | } | ||
| 656 | |||
| 657 | |||
| 658 | return groupType; | ||
| 659 | } | ||
| 660 | |||
| 661 | /// <summary> | ||
| 662 | /// Parses an action text element. | ||
| 663 | /// </summary> | ||
| 664 | /// <param name="node">Element to parse.</param> | ||
| 665 | private void ParseActionTextElement(XElement node) | ||
| 666 | { | ||
| 667 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 668 | string action = null; | ||
| 669 | string message = null; | ||
| 670 | string template = null; | ||
| 671 | |||
| 672 | foreach (var attrib in node.Attributes()) | ||
| 673 | { | ||
| 674 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 675 | { | ||
| 676 | switch (attrib.Name.LocalName) | ||
| 677 | { | ||
| 678 | case "Action": | ||
| 679 | action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 680 | break; | ||
| 681 | case "Message": | ||
| 682 | message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 683 | break; | ||
| 684 | case "Template": | ||
| 685 | template = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 686 | break; | ||
| 687 | default: | ||
| 688 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 689 | break; | ||
| 690 | } | ||
| 691 | } | ||
| 692 | else | ||
| 693 | { | ||
| 694 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 695 | } | ||
| 696 | } | ||
| 697 | |||
| 698 | if (null == action) | ||
| 699 | { | ||
| 700 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); | ||
| 701 | } | ||
| 702 | |||
| 703 | this.Core.ParseForExtensionElements(node); | ||
| 704 | |||
| 705 | if (!this.Core.EncounteredError) | ||
| 706 | { | ||
| 707 | this.Core.AddSymbol(new ActionTextSymbol(sourceLineNumbers) | ||
| 708 | { | ||
| 709 | Action = action, | ||
| 710 | Description = message, | ||
| 711 | Template = template, | ||
| 712 | }); | ||
| 713 | } | ||
| 714 | } | ||
| 715 | |||
| 716 | /// <summary> | ||
| 717 | /// Parses an ui text element. | ||
| 718 | /// </summary> | ||
| 719 | /// <param name="node">Element to parse.</param> | ||
| 720 | private void ParseUITextElement(XElement node) | ||
| 721 | { | ||
| 722 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 723 | Identifier id = null; | ||
| 724 | string text = null; | ||
| 725 | |||
| 726 | foreach (var attrib in node.Attributes()) | ||
| 727 | { | ||
| 728 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 729 | { | ||
| 730 | switch (attrib.Name.LocalName) | ||
| 731 | { | ||
| 732 | case "Id": | ||
| 733 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 734 | break; | ||
| 735 | case "Value": | ||
| 736 | text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 737 | break; | ||
| 738 | default: | ||
| 739 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 740 | break; | ||
| 741 | } | ||
| 742 | } | ||
| 743 | else | ||
| 744 | { | ||
| 745 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 746 | } | ||
| 747 | } | ||
| 748 | |||
| 749 | if (null == id) | ||
| 750 | { | ||
| 751 | id = this.Core.CreateIdentifier("txt", text); | ||
| 752 | } | ||
| 753 | |||
| 754 | this.Core.ParseForExtensionElements(node); | ||
| 755 | |||
| 756 | if (!this.Core.EncounteredError) | ||
| 757 | { | ||
| 758 | this.Core.AddSymbol(new UITextSymbol(sourceLineNumbers, id) | ||
| 759 | { | ||
| 760 | Text = text, | ||
| 761 | }); | ||
| 762 | } | ||
| 763 | } | ||
| 764 | |||
| 765 | /// <summary> | ||
| 766 | /// Parses a text style element. | ||
| 767 | /// </summary> | ||
| 768 | /// <param name="node">Element to parse.</param> | ||
| 769 | private void ParseTextStyleElement(XElement node) | ||
| 770 | { | ||
| 771 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 772 | Identifier id = null; | ||
| 773 | int? red = null; | ||
| 774 | int? green = null; | ||
| 775 | int? blue = null; | ||
| 776 | var bold = false; | ||
| 777 | var italic = false; | ||
| 778 | var strike = false; | ||
| 779 | var underline = false; | ||
| 780 | string faceName = null; | ||
| 781 | var size = "0"; | ||
| 782 | |||
| 783 | foreach (var attrib in node.Attributes()) | ||
| 784 | { | ||
| 785 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 786 | { | ||
| 787 | switch (attrib.Name.LocalName) | ||
| 788 | { | ||
| 789 | case "Id": | ||
| 790 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 791 | break; | ||
| 792 | |||
| 793 | // RGB Values | ||
| 794 | case "Red": | ||
| 795 | var redColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); | ||
| 796 | if (CompilerConstants.IllegalInteger != redColor) | ||
| 797 | { | ||
| 798 | red = redColor; | ||
| 799 | } | ||
| 800 | break; | ||
| 801 | case "Green": | ||
| 802 | var greenColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); | ||
| 803 | if (CompilerConstants.IllegalInteger != greenColor) | ||
| 804 | { | ||
| 805 | green = greenColor; | ||
| 806 | } | ||
| 807 | break; | ||
| 808 | case "Blue": | ||
| 809 | var blueColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); | ||
| 810 | if (CompilerConstants.IllegalInteger != blueColor) | ||
| 811 | { | ||
| 812 | blue = blueColor; | ||
| 813 | } | ||
| 814 | break; | ||
| 815 | |||
| 816 | // Style values | ||
| 817 | case "Bold": | ||
| 818 | bold = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 819 | break; | ||
| 820 | case "Italic": | ||
| 821 | italic = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 822 | break; | ||
| 823 | case "Strike": | ||
| 824 | strike = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 825 | break; | ||
| 826 | case "Underline": | ||
| 827 | underline = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 828 | break; | ||
| 829 | |||
| 830 | // Font values | ||
| 831 | case "FaceName": | ||
| 832 | faceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 833 | break; | ||
| 834 | case "Size": | ||
| 835 | size = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 836 | break; | ||
| 837 | |||
| 838 | default: | ||
| 839 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 840 | break; | ||
| 841 | } | ||
| 842 | } | ||
| 843 | else | ||
| 844 | { | ||
| 845 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 846 | } | ||
| 847 | } | ||
| 848 | |||
| 849 | if (null == id) | ||
| 850 | { | ||
| 851 | this.Core.CreateIdentifier("txs", faceName, size.ToString(), (red ?? 0).ToString(), (green ?? 0).ToString(), (blue ?? 0).ToString(), bold.ToString(), italic.ToString(), strike.ToString(), underline.ToString()); | ||
| 852 | } | ||
| 853 | |||
| 854 | if (null == faceName) | ||
| 855 | { | ||
| 856 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "FaceName")); | ||
| 857 | } | ||
| 858 | |||
| 859 | this.Core.ParseForExtensionElements(node); | ||
| 860 | |||
| 861 | if (!this.Core.EncounteredError) | ||
| 862 | { | ||
| 863 | this.Core.AddSymbol(new TextStyleSymbol(sourceLineNumbers, id) | ||
| 864 | { | ||
| 865 | FaceName = faceName, | ||
| 866 | LocalizedSize = size, | ||
| 867 | Red = red, | ||
| 868 | Green = green, | ||
| 869 | Blue = blue, | ||
| 870 | Bold = bold, | ||
| 871 | Italic = italic, | ||
| 872 | Strike = strike, | ||
| 873 | Underline = underline, | ||
| 874 | }); | ||
| 875 | } | ||
| 876 | } | ||
| 877 | |||
| 878 | /// <summary> | ||
| 879 | /// Parses a dialog element. | ||
| 880 | /// </summary> | ||
| 881 | /// <param name="node">Element to parse.</param> | ||
| 882 | private void ParseDialogElement(XElement node) | ||
| 883 | { | ||
| 884 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 885 | Identifier id = null; | ||
| 886 | var hidden = false; | ||
| 887 | var modal = true; | ||
| 888 | var minimize = true; | ||
| 889 | var customPalette = false; | ||
| 890 | var errorDialog = false; | ||
| 891 | var keepModeless = false; | ||
| 892 | var height = 0; | ||
| 893 | string title = null; | ||
| 894 | var leftScroll = false; | ||
| 895 | var rightAligned = false; | ||
| 896 | var rightToLeft = false; | ||
| 897 | var systemModal = false; | ||
| 898 | var trackDiskSpace = false; | ||
| 899 | var width = 0; | ||
| 900 | var x = 50; | ||
| 901 | var y = 50; | ||
| 902 | |||
| 903 | foreach (var attrib in node.Attributes()) | ||
| 904 | { | ||
| 905 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 906 | { | ||
| 907 | switch (attrib.Name.LocalName) | ||
| 908 | { | ||
| 909 | case "Id": | ||
| 910 | id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 911 | break; | ||
| 912 | case "Height": | ||
| 913 | height = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 914 | break; | ||
| 915 | case "Title": | ||
| 916 | title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 917 | break; | ||
| 918 | case "Width": | ||
| 919 | width = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 920 | break; | ||
| 921 | case "X": | ||
| 922 | x = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); | ||
| 923 | break; | ||
| 924 | case "Y": | ||
| 925 | y = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); | ||
| 926 | break; | ||
| 927 | case "CustomPalette": | ||
| 928 | customPalette = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 929 | break; | ||
| 930 | case "ErrorDialog": | ||
| 931 | errorDialog = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 932 | break; | ||
| 933 | case "Hidden": | ||
| 934 | hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 935 | break; | ||
| 936 | case "KeepModeless": | ||
| 937 | keepModeless = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 938 | break; | ||
| 939 | case "LeftScroll": | ||
| 940 | leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 941 | break; | ||
| 942 | case "Modeless": | ||
| 943 | modal = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 944 | break; | ||
| 945 | case "NoMinimize": | ||
| 946 | minimize = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 947 | break; | ||
| 948 | case "RightAligned": | ||
| 949 | rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 950 | break; | ||
| 951 | case "RightToLeft": | ||
| 952 | rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 953 | break; | ||
| 954 | case "SystemModal": | ||
| 955 | systemModal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 956 | break; | ||
| 957 | case "TrackDiskSpace": | ||
| 958 | trackDiskSpace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 959 | break; | ||
| 960 | |||
| 961 | default: | ||
| 962 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 963 | break; | ||
| 964 | } | ||
| 965 | } | ||
| 966 | else | ||
| 967 | { | ||
| 968 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 969 | } | ||
| 970 | } | ||
| 971 | |||
| 972 | if (null == id) | ||
| 973 | { | ||
| 974 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); | ||
| 975 | id = Identifier.Invalid; | ||
| 976 | } | ||
| 977 | |||
| 978 | ControlSymbol lastTabSymbol = null; | ||
| 979 | string cancelControl = null; | ||
| 980 | string defaultControl = null; | ||
| 981 | string firstControl = null; | ||
| 982 | |||
| 983 | foreach (var child in node.Elements()) | ||
| 984 | { | ||
| 985 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 986 | { | ||
| 987 | switch (child.Name.LocalName) | ||
| 988 | { | ||
| 989 | case "Control": | ||
| 990 | this.ParseControlElement(child, id.Id, SymbolDefinitionType.Control, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); | ||
| 991 | break; | ||
| 992 | default: | ||
| 993 | this.Core.UnexpectedElement(node, child); | ||
| 994 | break; | ||
| 995 | } | ||
| 996 | } | ||
| 997 | else | ||
| 998 | { | ||
| 999 | this.Core.ParseExtensionElement(node, child); | ||
| 1000 | } | ||
| 1001 | } | ||
| 1002 | |||
| 1003 | if (null != lastTabSymbol && null != lastTabSymbol.Control) | ||
| 1004 | { | ||
| 1005 | if (firstControl != lastTabSymbol.Control) | ||
| 1006 | { | ||
| 1007 | lastTabSymbol.NextControlRef = firstControl; | ||
| 1008 | } | ||
| 1009 | } | ||
| 1010 | |||
| 1011 | if (null == firstControl) | ||
| 1012 | { | ||
| 1013 | this.Core.Write(ErrorMessages.NoFirstControlSpecified(sourceLineNumbers, id.Id)); | ||
| 1014 | } | ||
| 1015 | |||
| 1016 | if (!this.Core.EncounteredError) | ||
| 1017 | { | ||
| 1018 | this.Core.AddSymbol(new DialogSymbol(sourceLineNumbers, id) | ||
| 1019 | { | ||
| 1020 | HCentering = x, | ||
| 1021 | VCentering = y, | ||
| 1022 | Width = width, | ||
| 1023 | Height = height, | ||
| 1024 | CustomPalette = customPalette, | ||
| 1025 | ErrorDialog = errorDialog, | ||
| 1026 | Visible = !hidden, | ||
| 1027 | Modal = modal, | ||
| 1028 | KeepModeless = keepModeless, | ||
| 1029 | LeftScroll = leftScroll, | ||
| 1030 | Minimize = minimize, | ||
| 1031 | RightAligned = rightAligned, | ||
| 1032 | RightToLeft = rightToLeft, | ||
| 1033 | SystemModal = systemModal, | ||
| 1034 | TrackDiskSpace = trackDiskSpace, | ||
| 1035 | Title = title, | ||
| 1036 | FirstControlRef = firstControl, | ||
| 1037 | DefaultControlRef = defaultControl, | ||
| 1038 | CancelControlRef = cancelControl, | ||
| 1039 | }); | ||
| 1040 | } | ||
| 1041 | } | ||
| 1042 | |||
| 1043 | /// <summary> | ||
| 1044 | /// Parses a control element. | ||
| 1045 | /// </summary> | ||
| 1046 | /// <param name="node">Element to parse.</param> | ||
| 1047 | /// <param name="dialog">Identifier for parent dialog.</param> | ||
| 1048 | /// <param name="symbolType">Table control belongs in.</param> | ||
| 1049 | /// <param name="lastTabSymbol">Last control in the tab order.</param> | ||
| 1050 | /// <param name="firstControl">Name of the first control in the tab order.</param> | ||
| 1051 | /// <param name="defaultControl">Name of the default control.</param> | ||
| 1052 | /// <param name="cancelControl">Name of the candle control.</param> | ||
| 1053 | private void ParseControlElement(XElement node, string dialog, SymbolDefinitionType symbolType, ref ControlSymbol lastTabSymbol, ref string firstControl, ref string defaultControl, ref string cancelControl) | ||
| 1054 | { | ||
| 1055 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1056 | Identifier controlId = null; | ||
| 1057 | var bits = new BitArray(32); | ||
| 1058 | string checkBoxPropertyRef = null; | ||
| 1059 | string checkboxValue = null; | ||
| 1060 | string controlType = null; | ||
| 1061 | var disabled = false; | ||
| 1062 | string height = null; | ||
| 1063 | string help = null; | ||
| 1064 | var isCancel = false; | ||
| 1065 | var isDefault = false; | ||
| 1066 | var notTabbable = false; | ||
| 1067 | string property = null; | ||
| 1068 | var publishOrder = 0; | ||
| 1069 | string sourceFile = null; | ||
| 1070 | string text = null; | ||
| 1071 | string tooltip = null; | ||
| 1072 | var radioButtonsType = RadioButtonType.NotSet; | ||
| 1073 | string width = null; | ||
| 1074 | string x = null; | ||
| 1075 | string y = null; | ||
| 1076 | |||
| 1077 | string defaultCondition = null; | ||
| 1078 | string enableCondition = null; | ||
| 1079 | string disableCondition = null; | ||
| 1080 | string hideCondition = null; | ||
| 1081 | string showCondition = null; | ||
| 1082 | |||
| 1083 | var hidden = false; | ||
| 1084 | var sunken = false; | ||
| 1085 | var indirect = false; | ||
| 1086 | var integer = false; | ||
| 1087 | var rightToLeft = false; | ||
| 1088 | var rightAligned = false; | ||
| 1089 | var leftScroll = false; | ||
| 1090 | |||
| 1091 | // The rest of the method relies on the control's Type, so we have to get that first. | ||
| 1092 | var typeAttribute = node.Attribute("Type"); | ||
| 1093 | if (null == typeAttribute) | ||
| 1094 | { | ||
| 1095 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); | ||
| 1096 | } | ||
| 1097 | else | ||
| 1098 | { | ||
| 1099 | controlType = this.Core.GetAttributeValue(sourceLineNumbers, typeAttribute); | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | string[] specialAttributes; | ||
| 1103 | switch (controlType) | ||
| 1104 | { | ||
| 1105 | case "Billboard": | ||
| 1106 | specialAttributes = null; | ||
| 1107 | notTabbable = true; | ||
| 1108 | disabled = true; | ||
| 1109 | |||
| 1110 | this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Billboard); | ||
| 1111 | break; | ||
| 1112 | case "Bitmap": | ||
| 1113 | specialAttributes = BitmapControlAttributes; | ||
| 1114 | notTabbable = true; | ||
| 1115 | disabled = true; | ||
| 1116 | break; | ||
| 1117 | case "CheckBox": | ||
| 1118 | specialAttributes = CheckboxControlAttributes; | ||
| 1119 | break; | ||
| 1120 | case "ComboBox": | ||
| 1121 | specialAttributes = ComboboxControlAttributes; | ||
| 1122 | break; | ||
| 1123 | case "DirectoryCombo": | ||
| 1124 | specialAttributes = VolumeControlAttributes; | ||
| 1125 | break; | ||
| 1126 | case "DirectoryList": | ||
| 1127 | specialAttributes = null; | ||
| 1128 | break; | ||
| 1129 | case "Edit": | ||
| 1130 | specialAttributes = EditControlAttributes; | ||
| 1131 | break; | ||
| 1132 | case "GroupBox": | ||
| 1133 | specialAttributes = null; | ||
| 1134 | notTabbable = true; | ||
| 1135 | break; | ||
| 1136 | case "Hyperlink": | ||
| 1137 | specialAttributes = HyperlinkControlAttributes; | ||
| 1138 | break; | ||
| 1139 | case "Icon": | ||
| 1140 | specialAttributes = IconControlAttributes; | ||
| 1141 | notTabbable = true; | ||
| 1142 | disabled = true; | ||
| 1143 | break; | ||
| 1144 | case "Line": | ||
| 1145 | specialAttributes = null; | ||
| 1146 | notTabbable = true; | ||
| 1147 | disabled = true; | ||
| 1148 | break; | ||
| 1149 | case "ListBox": | ||
| 1150 | specialAttributes = ListboxControlAttributes; | ||
| 1151 | break; | ||
| 1152 | case "ListView": | ||
| 1153 | specialAttributes = ListviewControlAttributes; | ||
| 1154 | break; | ||
| 1155 | case "MaskedEdit": | ||
| 1156 | specialAttributes = EditControlAttributes; | ||
| 1157 | break; | ||
| 1158 | case "PathEdit": | ||
| 1159 | specialAttributes = EditControlAttributes; | ||
| 1160 | break; | ||
| 1161 | case "ProgressBar": | ||
| 1162 | specialAttributes = ProgressControlAttributes; | ||
| 1163 | notTabbable = true; | ||
| 1164 | disabled = true; | ||
| 1165 | break; | ||
| 1166 | case "PushButton": | ||
| 1167 | specialAttributes = ButtonControlAttributes; | ||
| 1168 | break; | ||
| 1169 | case "RadioButtonGroup": | ||
| 1170 | specialAttributes = RadioControlAttributes; | ||
| 1171 | break; | ||
| 1172 | case "ScrollableText": | ||
| 1173 | specialAttributes = null; | ||
| 1174 | break; | ||
| 1175 | case "SelectionTree": | ||
| 1176 | specialAttributes = null; | ||
| 1177 | break; | ||
| 1178 | case "Text": | ||
| 1179 | specialAttributes = TextControlAttributes; | ||
| 1180 | notTabbable = true; | ||
| 1181 | break; | ||
| 1182 | case "VolumeCostList": | ||
| 1183 | specialAttributes = VolumeControlAttributes; | ||
| 1184 | notTabbable = true; | ||
| 1185 | break; | ||
| 1186 | case "VolumeSelectCombo": | ||
| 1187 | specialAttributes = VolumeControlAttributes; | ||
| 1188 | break; | ||
| 1189 | default: | ||
| 1190 | specialAttributes = null; | ||
| 1191 | notTabbable = true; | ||
| 1192 | break; | ||
| 1193 | } | ||
| 1194 | |||
| 1195 | foreach (var attrib in node.Attributes()) | ||
| 1196 | { | ||
| 1197 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1198 | { | ||
| 1199 | switch (attrib.Name.LocalName) | ||
| 1200 | { | ||
| 1201 | case "Id": | ||
| 1202 | controlId = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 1203 | break; | ||
| 1204 | case "Type": // already processed | ||
| 1205 | break; | ||
| 1206 | case "Cancel": | ||
| 1207 | isCancel = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1208 | break; | ||
| 1209 | case "CheckBoxPropertyRef": | ||
| 1210 | checkBoxPropertyRef = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1211 | break; | ||
| 1212 | case "CheckBoxValue": | ||
| 1213 | checkboxValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1214 | break; | ||
| 1215 | case "Default": | ||
| 1216 | isDefault = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1217 | break; | ||
| 1218 | case "DefaultCondition": | ||
| 1219 | defaultCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1220 | break; | ||
| 1221 | case "EnableCondition": | ||
| 1222 | enableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1223 | break; | ||
| 1224 | case "DisableCondition": | ||
| 1225 | disableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1226 | break; | ||
| 1227 | case "HideCondition": | ||
| 1228 | hideCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1229 | break; | ||
| 1230 | case "ShowCondition": | ||
| 1231 | showCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1232 | break; | ||
| 1233 | case "Height": | ||
| 1234 | height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 1235 | break; | ||
| 1236 | case "Help": | ||
| 1237 | help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1238 | break; | ||
| 1239 | case "IconSize": | ||
| 1240 | var iconSizeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1241 | if (null != specialAttributes) | ||
| 1242 | { | ||
| 1243 | switch (iconSizeValue) | ||
| 1244 | { | ||
| 1245 | case "16": | ||
| 1246 | this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); | ||
| 1247 | break; | ||
| 1248 | case "32": | ||
| 1249 | this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); | ||
| 1250 | break; | ||
| 1251 | case "48": | ||
| 1252 | this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); | ||
| 1253 | this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); | ||
| 1254 | break; | ||
| 1255 | case "": | ||
| 1256 | break; | ||
| 1257 | default: | ||
| 1258 | this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48")); | ||
| 1259 | break; | ||
| 1260 | } | ||
| 1261 | } | ||
| 1262 | else | ||
| 1263 | { | ||
| 1264 | this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "Type")); | ||
| 1265 | } | ||
| 1266 | break; | ||
| 1267 | case "Property": | ||
| 1268 | property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1269 | break; | ||
| 1270 | case "TabSkip": | ||
| 1271 | notTabbable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1272 | break; | ||
| 1273 | case "Text": | ||
| 1274 | text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1275 | break; | ||
| 1276 | case "ToolTip": | ||
| 1277 | tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1278 | break; | ||
| 1279 | case "Width": | ||
| 1280 | width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 1281 | break; | ||
| 1282 | case "X": | ||
| 1283 | x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 1284 | break; | ||
| 1285 | case "Y": | ||
| 1286 | y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 1287 | break; | ||
| 1288 | case "Disabled": | ||
| 1289 | disabled = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1290 | break; | ||
| 1291 | case "Hidden": | ||
| 1292 | hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1293 | break; | ||
| 1294 | case "Sunken": | ||
| 1295 | sunken = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1296 | break; | ||
| 1297 | case "Indirect": | ||
| 1298 | indirect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1299 | break; | ||
| 1300 | case "Integer": | ||
| 1301 | integer = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1302 | break; | ||
| 1303 | case "RightToLeft": | ||
| 1304 | rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1305 | break; | ||
| 1306 | case "RightAligned": | ||
| 1307 | rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1308 | break; | ||
| 1309 | case "LeftScroll": | ||
| 1310 | leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1311 | break; | ||
| 1312 | default: | ||
| 1313 | var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | ||
| 1314 | if (null == specialAttributes || !this.Core.TrySetBitFromName(specialAttributes, attrib.Name.LocalName, attribValue, bits, 16)) | ||
| 1315 | { | ||
| 1316 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1317 | } | ||
| 1318 | break; | ||
| 1319 | } | ||
| 1320 | } | ||
| 1321 | else | ||
| 1322 | { | ||
| 1323 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1324 | } | ||
| 1325 | } | ||
| 1326 | |||
| 1327 | var attributes = this.Core.CreateIntegerFromBitArray(bits); | ||
| 1328 | |||
| 1329 | //if (disabled) | ||
| 1330 | //{ | ||
| 1331 | // attributes |= WindowsInstallerConstants.MsidbControlAttributesEnabled; // bit will be inverted when stored | ||
| 1332 | //} | ||
| 1333 | |||
| 1334 | if (null == height) | ||
| 1335 | { | ||
| 1336 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | if (null == width) | ||
| 1340 | { | ||
| 1341 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); | ||
| 1342 | } | ||
| 1343 | |||
| 1344 | if (null == x) | ||
| 1345 | { | ||
| 1346 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); | ||
| 1347 | } | ||
| 1348 | |||
| 1349 | if (null == y) | ||
| 1350 | { | ||
| 1351 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); | ||
| 1352 | } | ||
| 1353 | |||
| 1354 | if (null == controlId) | ||
| 1355 | { | ||
| 1356 | controlId = this.Core.CreateIdentifier("ctl", dialog, x, y, height, width); | ||
| 1357 | } | ||
| 1358 | |||
| 1359 | if (isCancel) | ||
| 1360 | { | ||
| 1361 | cancelControl = controlId.Id; | ||
| 1362 | } | ||
| 1363 | |||
| 1364 | if (isDefault) | ||
| 1365 | { | ||
| 1366 | defaultControl = controlId.Id; | ||
| 1367 | } | ||
| 1368 | |||
| 1369 | foreach (var child in node.Elements()) | ||
| 1370 | { | ||
| 1371 | if (CompilerCore.WixNamespace == child.Name.Namespace) | ||
| 1372 | { | ||
| 1373 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); | ||
| 1374 | switch (child.Name.LocalName) | ||
| 1375 | { | ||
| 1376 | case "Binary": | ||
| 1377 | this.ParseBinaryElement(child); | ||
| 1378 | break; | ||
| 1379 | case "ComboBox": | ||
| 1380 | this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); | ||
| 1381 | break; | ||
| 1382 | case "ListBox": | ||
| 1383 | this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); | ||
| 1384 | break; | ||
| 1385 | case "ListView": | ||
| 1386 | this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); | ||
| 1387 | break; | ||
| 1388 | case "Property": | ||
| 1389 | this.ParsePropertyElement(child); | ||
| 1390 | break; | ||
| 1391 | case "Publish": | ||
| 1392 | this.ParsePublishElement(child, dialog ?? String.Empty, controlId.Id, ref publishOrder); | ||
| 1393 | break; | ||
| 1394 | case "RadioButtonGroup": | ||
| 1395 | radioButtonsType = this.ParseRadioButtonGroupElement(child, property, radioButtonsType); | ||
| 1396 | break; | ||
| 1397 | case "Subscribe": | ||
| 1398 | this.ParseSubscribeElement(child, dialog, controlId.Id); | ||
| 1399 | break; | ||
| 1400 | case "Text": | ||
| 1401 | foreach (var attrib in child.Attributes()) | ||
| 1402 | { | ||
| 1403 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1404 | { | ||
| 1405 | switch (attrib.Name.LocalName) | ||
| 1406 | { | ||
| 1407 | case "SourceFile": | ||
| 1408 | sourceFile = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); | ||
| 1409 | break; | ||
| 1410 | case "Value": | ||
| 1411 | text = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); | ||
| 1412 | break; | ||
| 1413 | default: | ||
| 1414 | this.Core.UnexpectedAttribute(child, attrib); | ||
| 1415 | break; | ||
| 1416 | } | ||
| 1417 | } | ||
| 1418 | else | ||
| 1419 | { | ||
| 1420 | this.Core.ParseExtensionAttribute(child, attrib); | ||
| 1421 | } | ||
| 1422 | } | ||
| 1423 | |||
| 1424 | this.Core.InnerTextDisallowed(child); | ||
| 1425 | |||
| 1426 | if (!String.IsNullOrEmpty(text) && null != sourceFile) | ||
| 1427 | { | ||
| 1428 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "SourceFile", "Value")); | ||
| 1429 | } | ||
| 1430 | break; | ||
| 1431 | default: | ||
| 1432 | this.Core.UnexpectedElement(node, child); | ||
| 1433 | break; | ||
| 1434 | } | ||
| 1435 | } | ||
| 1436 | else | ||
| 1437 | { | ||
| 1438 | this.Core.ParseExtensionElement(node, child); | ||
| 1439 | } | ||
| 1440 | } | ||
| 1441 | |||
| 1442 | this.Core.InnerTextDisallowed(node); | ||
| 1443 | |||
| 1444 | // If the radio buttons have icons, then we need to add the icon attribute. | ||
| 1445 | switch (radioButtonsType) | ||
| 1446 | { | ||
| 1447 | case RadioButtonType.Bitmap: | ||
| 1448 | attributes |= WindowsInstallerConstants.MsidbControlAttributesBitmap; | ||
| 1449 | break; | ||
| 1450 | case RadioButtonType.Icon: | ||
| 1451 | attributes |= WindowsInstallerConstants.MsidbControlAttributesIcon; | ||
| 1452 | break; | ||
| 1453 | case RadioButtonType.Text: | ||
| 1454 | // Text is the default so nothing needs to be added bits | ||
| 1455 | break; | ||
| 1456 | } | ||
| 1457 | |||
| 1458 | // the logic for creating control rows is a little tricky because of the way tabable controls are set | ||
| 1459 | IntermediateSymbol symbol = null; | ||
| 1460 | if (!this.Core.EncounteredError) | ||
| 1461 | { | ||
| 1462 | if ("CheckBox" == controlType) | ||
| 1463 | { | ||
| 1464 | if (String.IsNullOrEmpty(property) && String.IsNullOrEmpty(checkBoxPropertyRef)) | ||
| 1465 | { | ||
| 1466 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef", true)); | ||
| 1467 | } | ||
| 1468 | else if (!String.IsNullOrEmpty(property) && !String.IsNullOrEmpty(checkBoxPropertyRef)) | ||
| 1469 | { | ||
| 1470 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef")); | ||
| 1471 | } | ||
| 1472 | else if (!String.IsNullOrEmpty(property)) | ||
| 1473 | { | ||
| 1474 | this.Core.AddSymbol(new CheckBoxSymbol(sourceLineNumbers) | ||
| 1475 | { | ||
| 1476 | Property = property, | ||
| 1477 | Value = checkboxValue, | ||
| 1478 | }); | ||
| 1479 | } | ||
| 1480 | else | ||
| 1481 | { | ||
| 1482 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CheckBox, checkBoxPropertyRef); | ||
| 1483 | } | ||
| 1484 | } | ||
| 1485 | |||
| 1486 | var id = new Identifier(controlId.Access, dialog, controlId.Id); | ||
| 1487 | |||
| 1488 | if (SymbolDefinitionType.BBControl == symbolType) | ||
| 1489 | { | ||
| 1490 | var bbSymbol = this.Core.AddSymbol(new BBControlSymbol(sourceLineNumbers, id) | ||
| 1491 | { | ||
| 1492 | BillboardRef = dialog, | ||
| 1493 | BBControl = controlId.Id, | ||
| 1494 | Type = controlType, | ||
| 1495 | Attributes = attributes, | ||
| 1496 | Enabled = !disabled, | ||
| 1497 | Indirect = indirect, | ||
| 1498 | Integer = integer, | ||
| 1499 | LeftScroll = leftScroll, | ||
| 1500 | RightAligned = rightAligned, | ||
| 1501 | RightToLeft = rightToLeft, | ||
| 1502 | Sunken = sunken, | ||
| 1503 | Visible = !hidden, | ||
| 1504 | Text = text, | ||
| 1505 | SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } | ||
| 1506 | }); | ||
| 1507 | |||
| 1508 | bbSymbol.Set((int)BBControlSymbolFields.X, x); | ||
| 1509 | bbSymbol.Set((int)BBControlSymbolFields.Y, y); | ||
| 1510 | bbSymbol.Set((int)BBControlSymbolFields.Width, width); | ||
| 1511 | bbSymbol.Set((int)BBControlSymbolFields.Height, height); | ||
| 1512 | |||
| 1513 | symbol = bbSymbol; | ||
| 1514 | } | ||
| 1515 | else | ||
| 1516 | { | ||
| 1517 | var controlSymbol = this.Core.AddSymbol(new ControlSymbol(sourceLineNumbers, id) | ||
| 1518 | { | ||
| 1519 | DialogRef = dialog, | ||
| 1520 | Control = controlId.Id, | ||
| 1521 | Type = controlType, | ||
| 1522 | Attributes = attributes, | ||
| 1523 | Enabled = !disabled, | ||
| 1524 | Indirect = indirect, | ||
| 1525 | Integer = integer, | ||
| 1526 | LeftScroll = leftScroll, | ||
| 1527 | RightAligned = rightAligned, | ||
| 1528 | RightToLeft = rightToLeft, | ||
| 1529 | Sunken = sunken, | ||
| 1530 | Visible = !hidden, | ||
| 1531 | Property = !String.IsNullOrEmpty(property) ? property : checkBoxPropertyRef, | ||
| 1532 | Text = text, | ||
| 1533 | Help = (null == tooltip && null == help) ? null : String.Concat(tooltip, "|", help), // Separator is required, even if only one is non-null.}; | ||
| 1534 | SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } | ||
| 1535 | }); | ||
| 1536 | |||
| 1537 | controlSymbol.Set((int)BBControlSymbolFields.X, x); | ||
| 1538 | controlSymbol.Set((int)BBControlSymbolFields.Y, y); | ||
| 1539 | controlSymbol.Set((int)BBControlSymbolFields.Width, width); | ||
| 1540 | controlSymbol.Set((int)BBControlSymbolFields.Height, height); | ||
| 1541 | |||
| 1542 | symbol = controlSymbol; | ||
| 1543 | } | ||
| 1544 | |||
| 1545 | if (!String.IsNullOrEmpty(defaultCondition)) | ||
| 1546 | { | ||
| 1547 | this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) | ||
| 1548 | { | ||
| 1549 | DialogRef = dialog, | ||
| 1550 | ControlRef = controlId.Id, | ||
| 1551 | Action = "Default", | ||
| 1552 | Condition = defaultCondition, | ||
| 1553 | }); | ||
| 1554 | } | ||
| 1555 | |||
| 1556 | if (!String.IsNullOrEmpty(enableCondition)) | ||
| 1557 | { | ||
| 1558 | this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) | ||
| 1559 | { | ||
| 1560 | DialogRef = dialog, | ||
| 1561 | ControlRef = controlId.Id, | ||
| 1562 | Action = "Enable", | ||
| 1563 | Condition = enableCondition, | ||
| 1564 | }); | ||
| 1565 | } | ||
| 1566 | |||
| 1567 | if (!String.IsNullOrEmpty(disableCondition)) | ||
| 1568 | { | ||
| 1569 | this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) | ||
| 1570 | { | ||
| 1571 | DialogRef = dialog, | ||
| 1572 | ControlRef = controlId.Id, | ||
| 1573 | Action = "Disable", | ||
| 1574 | Condition = disableCondition, | ||
| 1575 | }); | ||
| 1576 | } | ||
| 1577 | |||
| 1578 | if (!String.IsNullOrEmpty(hideCondition)) | ||
| 1579 | { | ||
| 1580 | this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) | ||
| 1581 | { | ||
| 1582 | DialogRef = dialog, | ||
| 1583 | ControlRef = controlId.Id, | ||
| 1584 | Action = "Hide", | ||
| 1585 | Condition = hideCondition, | ||
| 1586 | }); | ||
| 1587 | } | ||
| 1588 | |||
| 1589 | if (!String.IsNullOrEmpty(showCondition)) | ||
| 1590 | { | ||
| 1591 | this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) | ||
| 1592 | { | ||
| 1593 | DialogRef = dialog, | ||
| 1594 | ControlRef = controlId.Id, | ||
| 1595 | Action = "Show", | ||
| 1596 | Condition = showCondition, | ||
| 1597 | }); | ||
| 1598 | } | ||
| 1599 | } | ||
| 1600 | |||
| 1601 | if (!notTabbable) | ||
| 1602 | { | ||
| 1603 | if (symbol is ControlSymbol controlSymbol) | ||
| 1604 | { | ||
| 1605 | if (null != lastTabSymbol) | ||
| 1606 | { | ||
| 1607 | lastTabSymbol.NextControlRef = controlSymbol.Control; | ||
| 1608 | } | ||
| 1609 | lastTabSymbol = controlSymbol; | ||
| 1610 | } | ||
| 1611 | else if (symbol != null) | ||
| 1612 | { | ||
| 1613 | this.Core.Write(ErrorMessages.TabbableControlNotAllowedInBillboard(sourceLineNumbers, node.Name.LocalName, controlType)); | ||
| 1614 | } | ||
| 1615 | |||
| 1616 | if (null == firstControl) | ||
| 1617 | { | ||
| 1618 | firstControl = controlId.Id; | ||
| 1619 | } | ||
| 1620 | } | ||
| 1621 | |||
| 1622 | // bitmap and icon controls contain a foreign key into the binary table in the text column; | ||
| 1623 | // add a reference if the identifier of the binary entry is known during compilation | ||
| 1624 | if (("Bitmap" == controlType || "Icon" == controlType) && Common.IsIdentifier(text)) | ||
| 1625 | { | ||
| 1626 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); | ||
| 1627 | } | ||
| 1628 | } | ||
| 1629 | |||
| 1630 | /// <summary> | ||
| 1631 | /// Parses a publish control event element. | ||
| 1632 | /// </summary> | ||
| 1633 | /// <param name="node">Element to parse.</param> | ||
| 1634 | /// <param name="dialog">Identifier of parent dialog.</param> | ||
| 1635 | /// <param name="control">Identifier of parent control.</param> | ||
| 1636 | /// <param name="order">Relative order of controls.</param> | ||
| 1637 | private void ParsePublishElement(XElement node, string dialog, string control, ref int order) | ||
| 1638 | { | ||
| 1639 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1640 | string argument = null; | ||
| 1641 | string condition = null; | ||
| 1642 | string controlEvent = null; | ||
| 1643 | string property = null; | ||
| 1644 | |||
| 1645 | // give this control event a unique ordering | ||
| 1646 | order++; | ||
| 1647 | |||
| 1648 | foreach (var attrib in node.Attributes()) | ||
| 1649 | { | ||
| 1650 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1651 | { | ||
| 1652 | switch (attrib.Name.LocalName) | ||
| 1653 | { | ||
| 1654 | case "Condition": | ||
| 1655 | condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1656 | break; | ||
| 1657 | case "Control": | ||
| 1658 | if (null != control) | ||
| 1659 | { | ||
| 1660 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); | ||
| 1661 | } | ||
| 1662 | control = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 1663 | break; | ||
| 1664 | case "Dialog": | ||
| 1665 | if (null != dialog) | ||
| 1666 | { | ||
| 1667 | this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); | ||
| 1668 | } | ||
| 1669 | dialog = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 1670 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, dialog); | ||
| 1671 | break; | ||
| 1672 | case "Event": | ||
| 1673 | controlEvent = Compiler.UppercaseFirstChar(this.Core.GetAttributeValue(sourceLineNumbers, attrib)); | ||
| 1674 | break; | ||
| 1675 | case "Order": | ||
| 1676 | order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 2147483647); | ||
| 1677 | break; | ||
| 1678 | case "Property": | ||
| 1679 | property = String.Concat("[", this.Core.GetAttributeValue(sourceLineNumbers, attrib), "]"); | ||
| 1680 | break; | ||
| 1681 | case "Value": | ||
| 1682 | argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 1683 | break; | ||
| 1684 | default: | ||
| 1685 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1686 | break; | ||
| 1687 | } | ||
| 1688 | } | ||
| 1689 | else | ||
| 1690 | { | ||
| 1691 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1692 | } | ||
| 1693 | } | ||
| 1694 | |||
| 1695 | if (null == control) | ||
| 1696 | { | ||
| 1697 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control")); | ||
| 1698 | } | ||
| 1699 | |||
| 1700 | if (null == dialog) | ||
| 1701 | { | ||
| 1702 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dialog")); | ||
| 1703 | } | ||
| 1704 | |||
| 1705 | if (null == controlEvent && null == property) // need to specify at least one | ||
| 1706 | { | ||
| 1707 | this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); | ||
| 1708 | } | ||
| 1709 | else if (null != controlEvent && null != property) // cannot specify both | ||
| 1710 | { | ||
| 1711 | this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); | ||
| 1712 | } | ||
| 1713 | |||
| 1714 | if (null == argument) | ||
| 1715 | { | ||
| 1716 | if (null != controlEvent) | ||
| 1717 | { | ||
| 1718 | this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value", "Event")); | ||
| 1719 | } | ||
| 1720 | else if (null != property) | ||
| 1721 | { | ||
| 1722 | // if this is setting a property to null, put a special value in the argument column | ||
| 1723 | argument = "{}"; | ||
| 1724 | } | ||
| 1725 | } | ||
| 1726 | |||
| 1727 | this.Core.ParseForExtensionElements(node); | ||
| 1728 | |||
| 1729 | if (!this.Core.EncounteredError) | ||
| 1730 | { | ||
| 1731 | this.Core.AddSymbol(new ControlEventSymbol(sourceLineNumbers) | ||
| 1732 | { | ||
| 1733 | DialogRef = dialog, | ||
| 1734 | ControlRef = control, | ||
| 1735 | Event = controlEvent ?? property, | ||
| 1736 | Argument = argument, | ||
| 1737 | Condition = condition, | ||
| 1738 | Ordering = order | ||
| 1739 | }); | ||
| 1740 | } | ||
| 1741 | |||
| 1742 | if ("DoAction" == controlEvent && null != argument) | ||
| 1743 | { | ||
| 1744 | // if we're not looking at a standard action or a formatted string then create a reference | ||
| 1745 | // to the custom action. | ||
| 1746 | if (!WindowsInstallerStandard.IsStandardAction(argument) && !this.Core.ContainsProperty(argument)) | ||
| 1747 | { | ||
| 1748 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CustomAction, argument); | ||
| 1749 | } | ||
| 1750 | } | ||
| 1751 | |||
| 1752 | // if we're referring to a dialog but not through a property, add it to the references | ||
| 1753 | if (("NewDialog" == controlEvent || "SpawnDialog" == controlEvent || "SpawnWaitDialog" == controlEvent || "SelectionBrowse" == controlEvent) && Common.IsIdentifier(argument)) | ||
| 1754 | { | ||
| 1755 | this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, argument); | ||
| 1756 | } | ||
| 1757 | } | ||
| 1758 | |||
| 1759 | /// <summary> | ||
| 1760 | /// Parses a control subscription element. | ||
| 1761 | /// </summary> | ||
| 1762 | /// <param name="node">Element to parse.</param> | ||
| 1763 | /// <param name="dialog">Identifier of dialog.</param> | ||
| 1764 | /// <param name="control">Identifier of control.</param> | ||
| 1765 | private void ParseSubscribeElement(XElement node, string dialog, string control) | ||
| 1766 | { | ||
| 1767 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | ||
| 1768 | string controlAttribute = null; | ||
| 1769 | string eventMapping = null; | ||
| 1770 | |||
| 1771 | foreach (var attrib in node.Attributes()) | ||
| 1772 | { | ||
| 1773 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) | ||
| 1774 | { | ||
| 1775 | switch (attrib.Name.LocalName) | ||
| 1776 | { | ||
| 1777 | case "Attribute": | ||
| 1778 | controlAttribute = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); | ||
| 1779 | break; | ||
| 1780 | case "Event": | ||
| 1781 | eventMapping = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); | ||
| 1782 | break; | ||
| 1783 | default: | ||
| 1784 | this.Core.UnexpectedAttribute(node, attrib); | ||
| 1785 | break; | ||
| 1786 | } | ||
| 1787 | } | ||
| 1788 | else | ||
| 1789 | { | ||
| 1790 | this.Core.ParseExtensionAttribute(node, attrib); | ||
| 1791 | } | ||
| 1792 | } | ||
| 1793 | |||
| 1794 | this.Core.ParseForExtensionElements(node); | ||
| 1795 | |||
| 1796 | if (!this.Core.EncounteredError) | ||
| 1797 | { | ||
| 1798 | this.Core.AddSymbol(new EventMappingSymbol(sourceLineNumbers) | ||
| 1799 | { | ||
| 1800 | DialogRef = dialog, | ||
| 1801 | ControlRef = control, | ||
| 1802 | Event = eventMapping, | ||
| 1803 | Attribute = controlAttribute, | ||
| 1804 | }); | ||
| 1805 | } | ||
| 1806 | } | ||
| 1807 | } | ||
| 1808 | } | ||
diff --git a/src/wix/WixToolset.Core/ComponentKeyPath.cs b/src/wix/WixToolset.Core/ComponentKeyPath.cs new file mode 100644 index 00000000..8e9c5776 --- /dev/null +++ b/src/wix/WixToolset.Core/ComponentKeyPath.cs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | internal class ComponentKeyPath : IComponentKeyPath | ||
| 9 | { | ||
| 10 | /// <summary> | ||
| 11 | /// Identifier of the resource to be a key path. | ||
| 12 | /// </summary> | ||
| 13 | public string Id { get; set; } | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Indicates whether the key path was explicitly set for this resource. | ||
| 17 | /// </summary> | ||
| 18 | public bool Explicit { get; set; } | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Type of resource to be the key path. | ||
| 22 | /// </summary> | ||
| 23 | public PossibleKeyPathType Type { get; set; } | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/src/wix/WixToolset.Core/DecompileContext.cs b/src/wix/WixToolset.Core/DecompileContext.cs new file mode 100644 index 00000000..a7ec03fd --- /dev/null +++ b/src/wix/WixToolset.Core/DecompileContext.cs | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Extensibility; | ||
| 9 | using WixToolset.Extensibility.Data; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | internal class DecompileContext : IDecompileContext | ||
| 13 | { | ||
| 14 | internal DecompileContext(IServiceProvider serviceProvider) | ||
| 15 | { | ||
| 16 | this.ServiceProvider = serviceProvider; | ||
| 17 | } | ||
| 18 | |||
| 19 | public IServiceProvider ServiceProvider { get; } | ||
| 20 | |||
| 21 | public string DecompilePath { get; set; } | ||
| 22 | |||
| 23 | public OutputType DecompileType { get; set; } | ||
| 24 | |||
| 25 | public IReadOnlyCollection<IDecompilerExtension> Extensions { get; set; } | ||
| 26 | |||
| 27 | public string ExtractFolder { get; set; } | ||
| 28 | |||
| 29 | public string CabinetExtractFolder { get; set; } | ||
| 30 | |||
| 31 | public string BaseSourcePath { get; set; } | ||
| 32 | |||
| 33 | public string IntermediateFolder { get; set; } | ||
| 34 | |||
| 35 | public bool IsAdminImage { get; set; } | ||
| 36 | |||
| 37 | public string OutputPath { get; set; } | ||
| 38 | |||
| 39 | public bool SuppressCustomTables { get; set; } | ||
| 40 | |||
| 41 | public bool SuppressDroppingEmptyTables { get; set; } | ||
| 42 | |||
| 43 | public bool SuppressExtractCabinets { get; set; } | ||
| 44 | |||
| 45 | public bool SuppressUI { get; set; } | ||
| 46 | |||
| 47 | public bool TreatProductAsModule { get; set; } | ||
| 48 | } | ||
| 49 | } | ||
diff --git a/src/wix/WixToolset.Core/DecompileResult.cs b/src/wix/WixToolset.Core/DecompileResult.cs new file mode 100644 index 00000000..fc24cab7 --- /dev/null +++ b/src/wix/WixToolset.Core/DecompileResult.cs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.Xml.Linq; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Extensibility.Data; | ||
| 9 | |||
| 10 | internal class DecompileResult : IDecompileResult | ||
| 11 | { | ||
| 12 | public XDocument Document { get; set; } | ||
| 13 | |||
| 14 | public IReadOnlyCollection<string> ExtractedFilePaths { get; set; } | ||
| 15 | |||
| 16 | public Platform? Platform { get; set; } | ||
| 17 | } | ||
| 18 | } | ||
diff --git a/src/wix/WixToolset.Core/Decompiler.cs b/src/wix/WixToolset.Core/Decompiler.cs new file mode 100644 index 00000000..859f582b --- /dev/null +++ b/src/wix/WixToolset.Core/Decompiler.cs | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility; | ||
| 7 | using WixToolset.Extensibility.Data; | ||
| 8 | using WixToolset.Extensibility.Services; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Decompiler of the WiX toolset. | ||
| 12 | /// </summary> | ||
| 13 | internal class Decompiler : IDecompiler | ||
| 14 | { | ||
| 15 | internal Decompiler(IServiceProvider serviceProvider) | ||
| 16 | { | ||
| 17 | this.ServiceProvider = serviceProvider; | ||
| 18 | } | ||
| 19 | |||
| 20 | public IServiceProvider ServiceProvider { get; } | ||
| 21 | |||
| 22 | public IDecompileResult Decompile(IDecompileContext context) | ||
| 23 | { | ||
| 24 | // Pre-decompile. | ||
| 25 | // | ||
| 26 | foreach (var extension in context.Extensions) | ||
| 27 | { | ||
| 28 | extension.PreDecompile(context); | ||
| 29 | } | ||
| 30 | |||
| 31 | // Decompile. | ||
| 32 | // | ||
| 33 | var result = this.BackendDecompile(context); | ||
| 34 | |||
| 35 | if (result != null) | ||
| 36 | { | ||
| 37 | // Post-decompile. | ||
| 38 | // | ||
| 39 | foreach (var extension in context.Extensions) | ||
| 40 | { | ||
| 41 | extension.PostDecompile(result); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | return result; | ||
| 46 | } | ||
| 47 | |||
| 48 | private IDecompileResult BackendDecompile(IDecompileContext context) | ||
| 49 | { | ||
| 50 | var extensionManager = context.ServiceProvider.GetService<IExtensionManager>(); | ||
| 51 | |||
| 52 | var backendFactories = extensionManager.GetServices<IBackendFactory>(); | ||
| 53 | |||
| 54 | foreach (var factory in backendFactories) | ||
| 55 | { | ||
| 56 | if (factory.TryCreateBackend(context.DecompileType.ToString(), context.OutputPath, out var backend)) | ||
| 57 | { | ||
| 58 | var result = backend.Decompile(context); | ||
| 59 | return result; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | // TODO: messaging that a backend could not be found to decompile the decompile type? | ||
| 64 | |||
| 65 | return null; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs new file mode 100644 index 00000000..cfa78623 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs | |||
| @@ -0,0 +1,176 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Core.Bind; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 12 | using WixToolset.Extensibility.Data; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | internal class BackendHelper : IBackendHelper | ||
| 16 | { | ||
| 17 | private static readonly string[] ReservedFileNames = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; | ||
| 18 | |||
| 19 | public BackendHelper(IServiceProvider serviceProvider) | ||
| 20 | { | ||
| 21 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 22 | } | ||
| 23 | |||
| 24 | private IMessaging Messaging { get; } | ||
| 25 | |||
| 26 | public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) | ||
| 27 | { | ||
| 28 | return new FileFacade(file, assembly); | ||
| 29 | } | ||
| 30 | |||
| 31 | public IFileFacade CreateFileFacade(FileRow fileRow) | ||
| 32 | { | ||
| 33 | return new FileFacade(fileRow); | ||
| 34 | } | ||
| 35 | |||
| 36 | public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) | ||
| 37 | { | ||
| 38 | return new FileFacade(true, fileSymbol); | ||
| 39 | } | ||
| 40 | |||
| 41 | public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) | ||
| 42 | { | ||
| 43 | var sourceFullPath = this.GetValidatedFullPath(sourceLineNumbers, source); | ||
| 44 | |||
| 45 | var destinationFullPath = this.GetValidatedFullPath(sourceLineNumbers, destination); | ||
| 46 | |||
| 47 | return (String.IsNullOrEmpty(sourceFullPath) || String.IsNullOrEmpty(destinationFullPath)) ? null : new FileTransfer | ||
| 48 | { | ||
| 49 | Source = sourceFullPath, | ||
| 50 | Destination = destinationFullPath, | ||
| 51 | Move = move, | ||
| 52 | SourceLineNumbers = sourceLineNumbers, | ||
| 53 | Redundant = String.Equals(sourceFullPath, destinationFullPath, StringComparison.OrdinalIgnoreCase) | ||
| 54 | }; | ||
| 55 | } | ||
| 56 | |||
| 57 | public string CreateGuid() | ||
| 58 | { | ||
| 59 | return Common.GenerateGuid(); | ||
| 60 | } | ||
| 61 | |||
| 62 | public string CreateGuid(Guid namespaceGuid, string value) | ||
| 63 | { | ||
| 64 | return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); | ||
| 65 | } | ||
| 66 | |||
| 67 | public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) | ||
| 68 | { | ||
| 69 | return new ResolvedDirectory | ||
| 70 | { | ||
| 71 | DirectoryParent = directoryParent, | ||
| 72 | Name = name | ||
| 73 | }; | ||
| 74 | } | ||
| 75 | |||
| 76 | public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles) | ||
| 77 | { | ||
| 78 | var command = new ExtractEmbeddedFilesCommand(this, embeddedFiles); | ||
| 79 | command.Execute(); | ||
| 80 | |||
| 81 | return command.TrackedFiles; | ||
| 82 | } | ||
| 83 | |||
| 84 | public string GenerateIdentifier(string prefix, params string[] args) | ||
| 85 | { | ||
| 86 | return Common.GenerateIdentifier(prefix, args); | ||
| 87 | } | ||
| 88 | |||
| 89 | public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) | ||
| 90 | { | ||
| 91 | return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); | ||
| 92 | } | ||
| 93 | |||
| 94 | public int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) | ||
| 95 | { | ||
| 96 | return Common.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); | ||
| 97 | } | ||
| 98 | |||
| 99 | public string GetMsiFileName(string value, bool source, bool longName) | ||
| 100 | { | ||
| 101 | return Common.GetName(value, source, longName); | ||
| 102 | } | ||
| 103 | |||
| 104 | public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache) | ||
| 105 | { | ||
| 106 | var command = new ResolveDelayedFieldsCommand(this.Messaging, delayedFields, variableCache); | ||
| 107 | command.Execute(); | ||
| 108 | } | ||
| 109 | |||
| 110 | public string[] SplitMsiFileName(string value) | ||
| 111 | { | ||
| 112 | return Common.GetNames(value); | ||
| 113 | } | ||
| 114 | |||
| 115 | public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) | ||
| 116 | { | ||
| 117 | return new TrackedFile(path, type, sourceLineNumbers); | ||
| 118 | } | ||
| 119 | |||
| 120 | public bool IsValidBinderVariable(string variable) | ||
| 121 | { | ||
| 122 | return Common.IsValidBinderVariable(variable); | ||
| 123 | } | ||
| 124 | |||
| 125 | public bool IsValidFourPartVersion(string version) | ||
| 126 | { | ||
| 127 | return Common.IsValidFourPartVersion(version); | ||
| 128 | } | ||
| 129 | |||
| 130 | public bool IsValidIdentifier(string id) | ||
| 131 | { | ||
| 132 | return Common.IsIdentifier(id); | ||
| 133 | } | ||
| 134 | |||
| 135 | public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) | ||
| 136 | { | ||
| 137 | return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); | ||
| 138 | } | ||
| 139 | |||
| 140 | public bool IsValidShortFilename(string filename, bool allowWildcards) | ||
| 141 | { | ||
| 142 | return Common.IsValidShortFilename(filename, allowWildcards); | ||
| 143 | } | ||
| 144 | |||
| 145 | private string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path) | ||
| 146 | { | ||
| 147 | try | ||
| 148 | { | ||
| 149 | var result = Path.GetFullPath(path); | ||
| 150 | |||
| 151 | var filename = Path.GetFileName(result); | ||
| 152 | |||
| 153 | foreach (var reservedName in ReservedFileNames) | ||
| 154 | { | ||
| 155 | if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase)) | ||
| 156 | { | ||
| 157 | this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); | ||
| 158 | return null; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | return result; | ||
| 163 | } | ||
| 164 | catch (ArgumentException) | ||
| 165 | { | ||
| 166 | this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); | ||
| 167 | } | ||
| 168 | catch (PathTooLongException) | ||
| 169 | { | ||
| 170 | this.Messaging.Write(ErrorMessages.PathTooLong(sourceLineNumbers, path)); | ||
| 171 | } | ||
| 172 | |||
| 173 | return null; | ||
| 174 | } | ||
| 175 | } | ||
| 176 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs new file mode 100644 index 00000000..2340ed9e --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs | |||
| @@ -0,0 +1,233 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Reflection; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Extensibility; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | internal class ExtensionManager : IExtensionManager | ||
| 15 | { | ||
| 16 | private const string UserWixFolderName = ".wix4"; | ||
| 17 | private const string MachineWixFolderName = "WixToolset4"; | ||
| 18 | private const string ExtensionsFolderName = "extensions"; | ||
| 19 | |||
| 20 | private readonly List<IExtensionFactory> extensionFactories = new List<IExtensionFactory>(); | ||
| 21 | private readonly Dictionary<Type, List<object>> loadedExtensionsByType = new Dictionary<Type, List<object>>(); | ||
| 22 | |||
| 23 | public ExtensionManager(IWixToolsetCoreServiceProvider serviceProvider) | ||
| 24 | { | ||
| 25 | this.ServiceProvider = serviceProvider; | ||
| 26 | } | ||
| 27 | |||
| 28 | private IWixToolsetCoreServiceProvider ServiceProvider { get; } | ||
| 29 | |||
| 30 | public void Add(Assembly extensionAssembly) | ||
| 31 | { | ||
| 32 | var types = extensionAssembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && typeof(IExtensionFactory).IsAssignableFrom(t)); | ||
| 33 | var factories = types.Select(this.CreateExtensionFactory).ToList(); | ||
| 34 | |||
| 35 | if (!factories.Any()) | ||
| 36 | { | ||
| 37 | var path = Path.GetFullPath(new Uri(extensionAssembly.CodeBase).LocalPath); | ||
| 38 | throw new WixException(ErrorMessages.InvalidExtension(path, "The extension does not implement IExtensionFactory. All extensions must have at least one implementation of IExtensionFactory.")); | ||
| 39 | } | ||
| 40 | |||
| 41 | this.extensionFactories.AddRange(factories); | ||
| 42 | } | ||
| 43 | |||
| 44 | public void Load(string extensionPath) | ||
| 45 | { | ||
| 46 | var checkPath = extensionPath; | ||
| 47 | var checkedPaths = new List<string> { checkPath }; | ||
| 48 | try | ||
| 49 | { | ||
| 50 | if (!TryLoadFromPath(checkPath, out var assembly) && !Path.IsPathRooted(extensionPath)) | ||
| 51 | { | ||
| 52 | if (TryParseExtensionReference(extensionPath, out var extensionId, out var extensionVersion)) | ||
| 53 | { | ||
| 54 | foreach (var cachePath in this.CacheLocations()) | ||
| 55 | { | ||
| 56 | var extensionFolder = Path.Combine(cachePath, extensionId); | ||
| 57 | |||
| 58 | var versionFolder = extensionVersion; | ||
| 59 | if (String.IsNullOrEmpty(versionFolder) && !TryFindLatestVersionInFolder(extensionFolder, out versionFolder)) | ||
| 60 | { | ||
| 61 | checkedPaths.Add(extensionFolder); | ||
| 62 | continue; | ||
| 63 | } | ||
| 64 | |||
| 65 | checkPath = Path.Combine(extensionFolder, versionFolder, "tools", extensionId + ".dll"); | ||
| 66 | checkedPaths.Add(checkPath); | ||
| 67 | |||
| 68 | if (TryLoadFromPath(checkPath, out assembly)) | ||
| 69 | { | ||
| 70 | break; | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | if (assembly == null) | ||
| 77 | { | ||
| 78 | throw new WixException(ErrorMessages.CouldNotFindExtensionInPaths(extensionPath, checkedPaths)); | ||
| 79 | } | ||
| 80 | |||
| 81 | this.Add(assembly); | ||
| 82 | } | ||
| 83 | catch (ReflectionTypeLoadException rtle) | ||
| 84 | { | ||
| 85 | throw new WixException(ErrorMessages.InvalidExtension(checkPath, String.Join(Environment.NewLine, rtle.LoaderExceptions.Select(le => le.ToString())))); | ||
| 86 | } | ||
| 87 | catch (WixException) | ||
| 88 | { | ||
| 89 | throw; | ||
| 90 | } | ||
| 91 | catch (Exception e) | ||
| 92 | { | ||
| 93 | throw new WixException(ErrorMessages.InvalidExtension(checkPath, e.Message), e); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | public IReadOnlyCollection<T> GetServices<T>() where T : class | ||
| 98 | { | ||
| 99 | if (!this.loadedExtensionsByType.TryGetValue(typeof(T), out var extensions)) | ||
| 100 | { | ||
| 101 | extensions = new List<object>(); | ||
| 102 | |||
| 103 | foreach (var factory in this.extensionFactories) | ||
| 104 | { | ||
| 105 | if (factory.TryCreateExtension(typeof(T), out var obj) && obj is T extension) | ||
| 106 | { | ||
| 107 | extensions.Add(extension); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | this.loadedExtensionsByType.Add(typeof(T), extensions); | ||
| 112 | } | ||
| 113 | |||
| 114 | return extensions.Cast<T>().ToList(); | ||
| 115 | } | ||
| 116 | |||
| 117 | private IExtensionFactory CreateExtensionFactory(Type type) | ||
| 118 | { | ||
| 119 | var constructor = type.GetConstructor(new[] { typeof(IWixToolsetCoreServiceProvider) }); | ||
| 120 | if (constructor != null) | ||
| 121 | { | ||
| 122 | return (IExtensionFactory)constructor.Invoke(new[] { this.ServiceProvider }); | ||
| 123 | } | ||
| 124 | |||
| 125 | return (IExtensionFactory)Activator.CreateInstance(type); | ||
| 126 | } | ||
| 127 | |||
| 128 | private IEnumerable<string> CacheLocations() | ||
| 129 | { | ||
| 130 | var path = Path.Combine(Environment.CurrentDirectory, UserWixFolderName, ExtensionsFolderName); | ||
| 131 | if (Directory.Exists(path)) | ||
| 132 | { | ||
| 133 | yield return path; | ||
| 134 | } | ||
| 135 | |||
| 136 | path = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); | ||
| 137 | path = Path.Combine(path, UserWixFolderName, ExtensionsFolderName); | ||
| 138 | if (Directory.Exists(path)) | ||
| 139 | { | ||
| 140 | yield return path; | ||
| 141 | } | ||
| 142 | |||
| 143 | if (Environment.Is64BitOperatingSystem) | ||
| 144 | { | ||
| 145 | path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), MachineWixFolderName, ExtensionsFolderName); | ||
| 146 | if (Directory.Exists(path)) | ||
| 147 | { | ||
| 148 | yield return path; | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), MachineWixFolderName, ExtensionsFolderName); | ||
| 153 | if (Directory.Exists(path)) | ||
| 154 | { | ||
| 155 | yield return path; | ||
| 156 | } | ||
| 157 | |||
| 158 | path = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath), ExtensionsFolderName); | ||
| 159 | if (Directory.Exists(path)) | ||
| 160 | { | ||
| 161 | yield return path; | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | private static bool TryParseExtensionReference(string extensionReference, out string extensionId, out string extensionVersion) | ||
| 166 | { | ||
| 167 | extensionId = extensionReference ?? String.Empty; | ||
| 168 | extensionVersion = String.Empty; | ||
| 169 | |||
| 170 | var index = extensionId.LastIndexOf('/'); | ||
| 171 | if (index > 0) | ||
| 172 | { | ||
| 173 | extensionVersion = extensionReference.Substring(index + 1); | ||
| 174 | extensionId = extensionReference.Substring(0, index); | ||
| 175 | |||
| 176 | if (!NuGet.Versioning.NuGetVersion.TryParse(extensionVersion, out _)) | ||
| 177 | { | ||
| 178 | return false; | ||
| 179 | } | ||
| 180 | |||
| 181 | if (String.IsNullOrEmpty(extensionId)) | ||
| 182 | { | ||
| 183 | return false; | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | return true; | ||
| 188 | } | ||
| 189 | |||
| 190 | private static bool TryFindLatestVersionInFolder(string basePath, out string foundVersionFolder) | ||
| 191 | { | ||
| 192 | foundVersionFolder = null; | ||
| 193 | |||
| 194 | try | ||
| 195 | { | ||
| 196 | NuGet.Versioning.NuGetVersion version = null; | ||
| 197 | foreach (var versionPath in Directory.GetDirectories(basePath)) | ||
| 198 | { | ||
| 199 | var versionFolder = Path.GetFileName(versionPath); | ||
| 200 | if (NuGet.Versioning.NuGetVersion.TryParse(versionFolder, out var checkVersion) && | ||
| 201 | (version == null || version < checkVersion)) | ||
| 202 | { | ||
| 203 | foundVersionFolder = versionFolder; | ||
| 204 | version = checkVersion; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | catch (IOException) | ||
| 209 | { | ||
| 210 | } | ||
| 211 | |||
| 212 | return !String.IsNullOrEmpty(foundVersionFolder); | ||
| 213 | } | ||
| 214 | |||
| 215 | private static bool TryLoadFromPath(string extensionPath, out Assembly assembly) | ||
| 216 | { | ||
| 217 | try | ||
| 218 | { | ||
| 219 | if (File.Exists(extensionPath)) | ||
| 220 | { | ||
| 221 | assembly = Assembly.LoadFrom(extensionPath); | ||
| 222 | return true; | ||
| 223 | } | ||
| 224 | } | ||
| 225 | catch (IOException e) when (e is FileLoadException || e is FileNotFoundException) | ||
| 226 | { | ||
| 227 | } | ||
| 228 | |||
| 229 | assembly = null; | ||
| 230 | return false; | ||
| 231 | } | ||
| 232 | } | ||
| 233 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs new file mode 100644 index 00000000..f85d4842 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs | |||
| @@ -0,0 +1,172 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Symbols; | ||
| 9 | using WixToolset.Data.WindowsInstaller; | ||
| 10 | using WixToolset.Data.WindowsInstaller.Rows; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | |||
| 13 | internal class FileFacade : IFileFacade | ||
| 14 | { | ||
| 15 | public FileFacade(FileSymbol file, AssemblySymbol assembly) | ||
| 16 | { | ||
| 17 | this.FileSymbol = file; | ||
| 18 | this.AssemblySymbol = assembly; | ||
| 19 | |||
| 20 | this.Identifier = file.Id; | ||
| 21 | this.ComponentRef = file.ComponentRef; | ||
| 22 | } | ||
| 23 | |||
| 24 | public FileFacade(bool fromModule, FileSymbol file) | ||
| 25 | { | ||
| 26 | this.FromModule = fromModule; | ||
| 27 | this.FileSymbol = file; | ||
| 28 | |||
| 29 | this.Identifier = file.Id; | ||
| 30 | this.ComponentRef = file.ComponentRef; | ||
| 31 | } | ||
| 32 | |||
| 33 | public FileFacade(FileRow row) | ||
| 34 | { | ||
| 35 | this.FromTransform = true; | ||
| 36 | this.FileRow = row; | ||
| 37 | |||
| 38 | this.Identifier = new Identifier(AccessModifier.Section, row.File); | ||
| 39 | this.ComponentRef = row.Component; | ||
| 40 | } | ||
| 41 | |||
| 42 | public bool FromModule { get; } | ||
| 43 | |||
| 44 | public bool FromTransform { get; } | ||
| 45 | |||
| 46 | private FileRow FileRow { get; } | ||
| 47 | |||
| 48 | private FileSymbol FileSymbol { get; } | ||
| 49 | |||
| 50 | private AssemblySymbol AssemblySymbol { get; } | ||
| 51 | |||
| 52 | public string Id => this.Identifier.Id; | ||
| 53 | |||
| 54 | public Identifier Identifier { get; } | ||
| 55 | |||
| 56 | public string ComponentRef { get; } | ||
| 57 | |||
| 58 | public int DiskId | ||
| 59 | { | ||
| 60 | get => this.FileRow == null ? this.FileSymbol.DiskId ?? 1 : this.FileRow.DiskId; | ||
| 61 | set | ||
| 62 | { | ||
| 63 | if (this.FileRow == null) | ||
| 64 | { | ||
| 65 | this.FileSymbol.DiskId = value; | ||
| 66 | } | ||
| 67 | else | ||
| 68 | { | ||
| 69 | this.FileRow.DiskId = value; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | public string FileName => this.FileRow == null ? this.FileSymbol.Name : this.FileRow.FileName; | ||
| 75 | |||
| 76 | public int FileSize | ||
| 77 | { | ||
| 78 | get => this.FileRow == null ? this.FileSymbol.FileSize : this.FileRow.FileSize; | ||
| 79 | set | ||
| 80 | { | ||
| 81 | if (this.FileRow == null) | ||
| 82 | { | ||
| 83 | this.FileSymbol.FileSize = value; | ||
| 84 | } | ||
| 85 | else | ||
| 86 | { | ||
| 87 | this.FileRow.FileSize = value; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | public string Language | ||
| 93 | { | ||
| 94 | get => this.FileRow == null ? this.FileSymbol.Language : this.FileRow.Language; | ||
| 95 | set | ||
| 96 | { | ||
| 97 | if (this.FileRow == null) | ||
| 98 | { | ||
| 99 | this.FileSymbol.Language = value; | ||
| 100 | } | ||
| 101 | else | ||
| 102 | { | ||
| 103 | this.FileRow.Language = value; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | public int? PatchGroup => this.FileRow == null ? this.FileSymbol.PatchGroup : null; | ||
| 109 | |||
| 110 | public int Sequence | ||
| 111 | { | ||
| 112 | get => this.FileRow == null ? this.FileSymbol.Sequence : this.FileRow.Sequence; | ||
| 113 | set | ||
| 114 | { | ||
| 115 | if (this.FileRow == null) | ||
| 116 | { | ||
| 117 | this.FileSymbol.Sequence = value; | ||
| 118 | } | ||
| 119 | else | ||
| 120 | { | ||
| 121 | this.FileRow.Sequence = value; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers; | ||
| 127 | |||
| 128 | public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source; | ||
| 129 | |||
| 130 | public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; | ||
| 131 | |||
| 132 | public bool Uncompressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed; | ||
| 133 | |||
| 134 | public string Version | ||
| 135 | { | ||
| 136 | get => this.FileRow == null ? this.FileSymbol.Version : this.FileRow.Version; | ||
| 137 | set | ||
| 138 | { | ||
| 139 | if (this.FileRow == null) | ||
| 140 | { | ||
| 141 | this.FileSymbol.Version = value; | ||
| 142 | } | ||
| 143 | else | ||
| 144 | { | ||
| 145 | this.FileRow.Version = value; | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblySymbol?.Type : null; | ||
| 151 | |||
| 152 | public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblySymbol?.ApplicationFileRef : throw new NotImplementedException(); | ||
| 153 | |||
| 154 | public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblySymbol?.ManifestFileRef : throw new NotImplementedException(); | ||
| 155 | |||
| 156 | /// <summary> | ||
| 157 | /// Gets the set of MsiAssemblyName rows created for this file. | ||
| 158 | /// </summary> | ||
| 159 | /// <value>RowCollection of MsiAssemblyName table.</value> | ||
| 160 | public List<MsiAssemblyNameSymbol> AssemblyNames { get; set; } | ||
| 161 | |||
| 162 | /// <summary> | ||
| 163 | /// Gets or sets the MsiFileHash row for this file. | ||
| 164 | /// </summary> | ||
| 165 | public MsiFileHashSymbol Hash { get; set; } | ||
| 166 | |||
| 167 | /// <summary> | ||
| 168 | /// Allows direct access to the underlying FileRow as requried for patching. | ||
| 169 | /// </summary> | ||
| 170 | public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException(); | ||
| 171 | } | ||
| 172 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs new file mode 100644 index 00000000..2cad7cce --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | internal class FileTransfer : IFileTransfer | ||
| 9 | { | ||
| 10 | public string Source { get; set; } | ||
| 11 | |||
| 12 | public string Destination { get; set; } | ||
| 13 | |||
| 14 | public bool Move { get; set; } | ||
| 15 | |||
| 16 | public SourceLineNumber SourceLineNumbers { get; set; } | ||
| 17 | |||
| 18 | public bool Redundant { get; set; } | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs b/src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs new file mode 100644 index 00000000..afcd9244 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Extensibility; | ||
| 8 | using WixToolset.Extensibility.Services; | ||
| 9 | |||
| 10 | internal class Messaging : IMessaging | ||
| 11 | { | ||
| 12 | private IMessageListener listener; | ||
| 13 | private readonly HashSet<int> suppressedWarnings = new HashSet<int>(); | ||
| 14 | private readonly HashSet<int> warningsAsErrors = new HashSet<int>(); | ||
| 15 | |||
| 16 | public bool EncounteredError { get; private set; } | ||
| 17 | |||
| 18 | public int LastErrorNumber { get; private set; } | ||
| 19 | |||
| 20 | public bool ShowVerboseMessages { get; set; } | ||
| 21 | |||
| 22 | public bool SuppressAllWarnings { get; set; } | ||
| 23 | |||
| 24 | public bool WarningsAsError { get; set; } | ||
| 25 | |||
| 26 | public void ElevateWarningMessage(int warningNumber) => this.warningsAsErrors.Add(warningNumber); | ||
| 27 | |||
| 28 | public void SetListener(IMessageListener listener) => this.listener = listener; | ||
| 29 | |||
| 30 | public void SuppressWarningMessage(int warningNumber) => this.suppressedWarnings.Add(warningNumber); | ||
| 31 | |||
| 32 | public void Write(Message message) | ||
| 33 | { | ||
| 34 | var level = this.CalculateMessageLevel(message); | ||
| 35 | |||
| 36 | if (level == MessageLevel.Nothing) | ||
| 37 | { | ||
| 38 | return; | ||
| 39 | } | ||
| 40 | |||
| 41 | if (level == MessageLevel.Error) | ||
| 42 | { | ||
| 43 | this.EncounteredError = true; | ||
| 44 | this.LastErrorNumber = message.Id; | ||
| 45 | } | ||
| 46 | |||
| 47 | if (this.listener != null) | ||
| 48 | { | ||
| 49 | this.listener.Write(message); | ||
| 50 | } | ||
| 51 | else if (level == MessageLevel.Error) | ||
| 52 | { | ||
| 53 | throw new WixException(message); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | public void Write(string message, bool verbose = false) | ||
| 58 | { | ||
| 59 | if (!verbose || this.ShowVerboseMessages) | ||
| 60 | { | ||
| 61 | this.listener?.Write(message); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Determines the level of this message, when taking into account warning-as-error, | ||
| 67 | /// warning level, verbosity level and message suppressed by the caller. | ||
| 68 | /// </summary> | ||
| 69 | /// <param name="message">Event arguments for the message.</param> | ||
| 70 | /// <returns>MessageLevel representing the level of this message.</returns> | ||
| 71 | private MessageLevel CalculateMessageLevel(Message message) | ||
| 72 | { | ||
| 73 | var level = message.Level; | ||
| 74 | |||
| 75 | if (level == MessageLevel.Verbose) | ||
| 76 | { | ||
| 77 | if (!this.ShowVerboseMessages) | ||
| 78 | { | ||
| 79 | level = MessageLevel.Nothing; | ||
| 80 | } | ||
| 81 | } | ||
| 82 | else if (level == MessageLevel.Warning) | ||
| 83 | { | ||
| 84 | if (this.SuppressAllWarnings || this.suppressedWarnings.Contains(message.Id)) | ||
| 85 | { | ||
| 86 | level = MessageLevel.Nothing; | ||
| 87 | } | ||
| 88 | else if (this.WarningsAsError || this.warningsAsErrors.Contains(message.Id)) | ||
| 89 | { | ||
| 90 | level = MessageLevel.Error; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | level = this.listener?.CalculateMessageLevel(this, message, level) ?? level; | ||
| 95 | |||
| 96 | return level; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs new file mode 100644 index 00000000..c1368190 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs | |||
| @@ -0,0 +1,863 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Xml; | ||
| 11 | using System.Xml.Linq; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Data.WindowsInstaller; | ||
| 15 | using WixToolset.Extensibility; | ||
| 16 | using WixToolset.Extensibility.Data; | ||
| 17 | using WixToolset.Extensibility.Services; | ||
| 18 | |||
| 19 | internal class ParseHelper : IParseHelper | ||
| 20 | { | ||
| 21 | public ParseHelper(IServiceProvider serviceProvider) | ||
| 22 | { | ||
| 23 | this.ServiceProvider = serviceProvider; | ||
| 24 | |||
| 25 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 26 | } | ||
| 27 | |||
| 28 | private IServiceProvider ServiceProvider { get; } | ||
| 29 | |||
| 30 | private IMessaging Messaging { get; } | ||
| 31 | |||
| 32 | private ISymbolDefinitionCreator Creator { get; set; } | ||
| 33 | |||
| 34 | public bool ContainsProperty(string possibleProperty) | ||
| 35 | { | ||
| 36 | return Common.ContainsProperty(possibleProperty); | ||
| 37 | } | ||
| 38 | |||
| 39 | public void CreateComplexReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) | ||
| 40 | { | ||
| 41 | |||
| 42 | section.AddSymbol(new WixComplexReferenceSymbol(sourceLineNumbers) | ||
| 43 | { | ||
| 44 | Parent = parentId, | ||
| 45 | ParentType = parentType, | ||
| 46 | ParentLanguage = parentLanguage, | ||
| 47 | Child = childId, | ||
| 48 | ChildType = childType, | ||
| 49 | IsPrimary = isPrimary | ||
| 50 | }); | ||
| 51 | |||
| 52 | this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId); | ||
| 53 | } | ||
| 54 | |||
| 55 | public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) | ||
| 56 | { | ||
| 57 | if (null == id) | ||
| 58 | { | ||
| 59 | id = this.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); | ||
| 60 | } | ||
| 61 | |||
| 62 | var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) | ||
| 63 | { | ||
| 64 | ParentDirectoryRef = parentId, | ||
| 65 | Name = name, | ||
| 66 | ShortName = shortName, | ||
| 67 | SourceName = sourceName, | ||
| 68 | SourceShortName = shortSourceName | ||
| 69 | }); | ||
| 70 | |||
| 71 | return symbol.Id; | ||
| 72 | } | ||
| 73 | |||
| 74 | public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary<string, string> sectionCachedInlinedDirectoryIds) | ||
| 75 | { | ||
| 76 | if (String.IsNullOrEmpty(parentId)) | ||
| 77 | { | ||
| 78 | throw new ArgumentNullException(nameof(parentId)); | ||
| 79 | } | ||
| 80 | |||
| 81 | if (String.IsNullOrEmpty(inlineSyntax)) | ||
| 82 | { | ||
| 83 | inlineSyntax = this.GetAttributeLongFilename(sourceLineNumbers, attribute, false, true); | ||
| 84 | } | ||
| 85 | |||
| 86 | if (String.IsNullOrEmpty(inlineSyntax)) | ||
| 87 | { | ||
| 88 | return parentId; | ||
| 89 | } | ||
| 90 | |||
| 91 | inlineSyntax = inlineSyntax.Trim('\\', '/'); | ||
| 92 | |||
| 93 | var cacheKey = String.Concat(parentId, ":", inlineSyntax); | ||
| 94 | |||
| 95 | if (!sectionCachedInlinedDirectoryIds.TryGetValue(cacheKey, out var id)) | ||
| 96 | { | ||
| 97 | var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, id: null, parentId, inlineSyntax); | ||
| 98 | |||
| 99 | id = identifier.Id; | ||
| 100 | } | ||
| 101 | else | ||
| 102 | { | ||
| 103 | this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id); | ||
| 104 | } | ||
| 105 | |||
| 106 | return id; | ||
| 107 | } | ||
| 108 | |||
| 109 | public string CreateGuid(Guid namespaceGuid, string value) | ||
| 110 | { | ||
| 111 | return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); | ||
| 112 | } | ||
| 113 | |||
| 114 | public Identifier CreateIdentifier(string prefix, params string[] args) | ||
| 115 | { | ||
| 116 | var id = Common.GenerateIdentifier(prefix, args); | ||
| 117 | return new Identifier(AccessModifier.Section, id); | ||
| 118 | } | ||
| 119 | |||
| 120 | public Identifier CreateIdentifierFromFilename(string filename) | ||
| 121 | { | ||
| 122 | var id = Common.GetIdentifierFromName(filename); | ||
| 123 | return new Identifier(AccessModifier.Section, id); | ||
| 124 | } | ||
| 125 | |||
| 126 | public string CreateIdentifierValueFromPlatform(string name, Platform currentPlatform, BurnPlatforms supportedPlatforms) | ||
| 127 | { | ||
| 128 | string suffix = null; | ||
| 129 | |||
| 130 | switch (currentPlatform) | ||
| 131 | { | ||
| 132 | case Platform.X86: | ||
| 133 | if ((supportedPlatforms & BurnPlatforms.X86) == BurnPlatforms.X86) | ||
| 134 | { | ||
| 135 | suffix = "_X86"; | ||
| 136 | } | ||
| 137 | break; | ||
| 138 | case Platform.X64: | ||
| 139 | if ((supportedPlatforms & BurnPlatforms.X64) == BurnPlatforms.X64) | ||
| 140 | { | ||
| 141 | suffix = "_X64"; | ||
| 142 | } | ||
| 143 | break; | ||
| 144 | case Platform.ARM64: | ||
| 145 | if ((supportedPlatforms & BurnPlatforms.ARM64) == BurnPlatforms.ARM64) | ||
| 146 | { | ||
| 147 | suffix = "_A64"; | ||
| 148 | } | ||
| 149 | break; | ||
| 150 | } | ||
| 151 | |||
| 152 | return suffix == null ? null : name + suffix; | ||
| 153 | } | ||
| 154 | |||
| 155 | public Identifier CreateRegistrySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId, bool escapeLeadingHash) | ||
| 156 | { | ||
| 157 | if (RegistryRootType.Unknown == root) | ||
| 158 | { | ||
| 159 | throw new ArgumentOutOfRangeException(nameof(root)); | ||
| 160 | } | ||
| 161 | |||
| 162 | if (null == key) | ||
| 163 | { | ||
| 164 | throw new ArgumentNullException(nameof(key)); | ||
| 165 | } | ||
| 166 | |||
| 167 | if (null == componentId) | ||
| 168 | { | ||
| 169 | throw new ArgumentNullException(nameof(componentId)); | ||
| 170 | } | ||
| 171 | |||
| 172 | // Escape the leading '#' character for string registry values. | ||
| 173 | if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal)) | ||
| 174 | { | ||
| 175 | value = String.Concat("#", value); | ||
| 176 | } | ||
| 177 | |||
| 178 | var id = this.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name)); | ||
| 179 | |||
| 180 | var symbol = section.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) | ||
| 181 | { | ||
| 182 | Root = root, | ||
| 183 | Key = key, | ||
| 184 | Name = name, | ||
| 185 | Value = value, | ||
| 186 | ComponentRef = componentId, | ||
| 187 | }); | ||
| 188 | |||
| 189 | return symbol.Id; | ||
| 190 | } | ||
| 191 | |||
| 192 | public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) | ||
| 193 | { | ||
| 194 | section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) | ||
| 195 | { | ||
| 196 | Table = symbolName, | ||
| 197 | PrimaryKeys = primaryKey | ||
| 198 | }); | ||
| 199 | } | ||
| 200 | |||
| 201 | public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) | ||
| 202 | { | ||
| 203 | section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) | ||
| 204 | { | ||
| 205 | Table = symbolName, | ||
| 206 | PrimaryKeys = String.Join("/", primaryKeys) | ||
| 207 | }); | ||
| 208 | } | ||
| 209 | |||
| 210 | public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) | ||
| 211 | { | ||
| 212 | this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKey); | ||
| 213 | } | ||
| 214 | |||
| 215 | public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) | ||
| 216 | { | ||
| 217 | this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKeys); | ||
| 218 | } | ||
| 219 | |||
| 220 | public void CreateWixGroupSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) | ||
| 221 | { | ||
| 222 | if (null == parentId || ComplexReferenceParentType.Unknown == parentType) | ||
| 223 | { | ||
| 224 | return; | ||
| 225 | } | ||
| 226 | |||
| 227 | if (null == childId) | ||
| 228 | { | ||
| 229 | throw new ArgumentNullException(nameof(childId)); | ||
| 230 | } | ||
| 231 | |||
| 232 | section.AddSymbol(new WixGroupSymbol(sourceLineNumbers) | ||
| 233 | { | ||
| 234 | ParentId = parentId, | ||
| 235 | ParentType = parentType, | ||
| 236 | ChildId = childId, | ||
| 237 | ChildType = childType, | ||
| 238 | }); | ||
| 239 | } | ||
| 240 | |||
| 241 | public void CreateWixSearchSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId) | ||
| 242 | { | ||
| 243 | // TODO: verify variable is not a standard bundle variable | ||
| 244 | if (variable == null) | ||
| 245 | { | ||
| 246 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable")); | ||
| 247 | } | ||
| 248 | |||
| 249 | section.AddSymbol(new WixSearchSymbol(sourceLineNumbers, id) | ||
| 250 | { | ||
| 251 | Variable = variable, | ||
| 252 | Condition = condition, | ||
| 253 | BundleExtensionRef = bundleExtensionId, | ||
| 254 | }); | ||
| 255 | |||
| 256 | if (after != null) | ||
| 257 | { | ||
| 258 | this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixSearch, after); | ||
| 259 | // TODO: We're currently defaulting to "always run after", which we will need to change... | ||
| 260 | this.CreateWixSearchRelationSymbol(section, sourceLineNumbers, id, after, 2); | ||
| 261 | } | ||
| 262 | |||
| 263 | if (!String.IsNullOrEmpty(bundleExtensionId)) | ||
| 264 | { | ||
| 265 | this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixBundleExtension, bundleExtensionId); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | public void CreateWixSearchRelationSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes) | ||
| 270 | { | ||
| 271 | section.AddSymbol(new WixSearchRelationSymbol(sourceLineNumbers, id) | ||
| 272 | { | ||
| 273 | ParentSearchRef = parentId, | ||
| 274 | Attributes = attributes, | ||
| 275 | }); | ||
| 276 | } | ||
| 277 | |||
| 278 | public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, Identifier identifier = null) | ||
| 279 | { | ||
| 280 | if (this.Creator == null) | ||
| 281 | { | ||
| 282 | this.CreateSymbolDefinitionCreator(); | ||
| 283 | } | ||
| 284 | |||
| 285 | if (!this.Creator.TryGetSymbolDefinitionByName(symbolName, out var symbolDefinition)) | ||
| 286 | { | ||
| 287 | throw new ArgumentException(nameof(symbolName)); | ||
| 288 | } | ||
| 289 | |||
| 290 | return this.CreateSymbol(section, sourceLineNumbers, symbolDefinition, identifier); | ||
| 291 | } | ||
| 292 | |||
| 293 | public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, Identifier identifier = null) | ||
| 294 | { | ||
| 295 | return section.AddSymbol(symbolDefinition.CreateSymbol(sourceLineNumbers, identifier)); | ||
| 296 | } | ||
| 297 | |||
| 298 | public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) | ||
| 299 | { | ||
| 300 | section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) | ||
| 301 | { | ||
| 302 | Table = tableDefinition.Name, | ||
| 303 | }); | ||
| 304 | |||
| 305 | // TODO: Check if the given table definition is a custom table. For now we have to assume that it isn't. | ||
| 306 | //this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableDefinition.Name); | ||
| 307 | } | ||
| 308 | |||
| 309 | public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName) | ||
| 310 | { | ||
| 311 | section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) | ||
| 312 | { | ||
| 313 | Table = tableName, | ||
| 314 | }); | ||
| 315 | |||
| 316 | if (this.Creator == null) | ||
| 317 | { | ||
| 318 | this.CreateSymbolDefinitionCreator(); | ||
| 319 | } | ||
| 320 | |||
| 321 | // TODO: The tableName may not be the same as the symbolName. For now, we have to assume that it is. | ||
| 322 | // We don't add custom table definitions to the tableDefinitions collection, | ||
| 323 | // so if it's not in there, it better be a custom table. If the Id is just wrong, | ||
| 324 | // instead of a custom table, we get an unresolved reference at link time. | ||
| 325 | if (!this.Creator.TryGetSymbolDefinitionByName(tableName, out var _)) | ||
| 326 | { | ||
| 327 | this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableName); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) | ||
| 332 | { | ||
| 333 | if (null == attribute) | ||
| 334 | { | ||
| 335 | throw new ArgumentNullException(nameof(attribute)); | ||
| 336 | } | ||
| 337 | |||
| 338 | var emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly; | ||
| 339 | var value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); | ||
| 340 | |||
| 341 | if (String.IsNullOrEmpty(value)) | ||
| 342 | { | ||
| 343 | if (canBeEmpty) | ||
| 344 | { | ||
| 345 | return String.Empty; | ||
| 346 | } | ||
| 347 | } | ||
| 348 | else | ||
| 349 | { | ||
| 350 | if (generatable && value == "*") | ||
| 351 | { | ||
| 352 | return value; | ||
| 353 | } | ||
| 354 | |||
| 355 | if (Guid.TryParse(value, out var guid)) | ||
| 356 | { | ||
| 357 | return guid.ToString("B").ToUpperInvariant(); | ||
| 358 | } | ||
| 359 | |||
| 360 | if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal)) | ||
| 361 | { | ||
| 362 | return value; | ||
| 363 | } | ||
| 364 | |||
| 365 | if (value.StartsWith("PUT-GUID-", StringComparison.OrdinalIgnoreCase) || | ||
| 366 | value.StartsWith("{PUT-GUID-", StringComparison.OrdinalIgnoreCase)) | ||
| 367 | { | ||
| 368 | this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 369 | } | ||
| 370 | else | ||
| 371 | { | ||
| 372 | this.Messaging.Write(ErrorMessages.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | return CompilerConstants.IllegalGuid; | ||
| 377 | } | ||
| 378 | |||
| 379 | public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 380 | { | ||
| 381 | var access = AccessModifier.Global; | ||
| 382 | var value = Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, EmptyRule.CanBeEmpty); | ||
| 383 | |||
| 384 | var separator = value.IndexOf(' '); | ||
| 385 | if (separator > 0) | ||
| 386 | { | ||
| 387 | var prefix = value.Substring(0, separator); | ||
| 388 | switch (prefix) | ||
| 389 | { | ||
| 390 | case "global": | ||
| 391 | case "public": | ||
| 392 | case "package": | ||
| 393 | access = AccessModifier.Global; | ||
| 394 | break; | ||
| 395 | |||
| 396 | case "internal": | ||
| 397 | case "library": | ||
| 398 | access = AccessModifier.Library; | ||
| 399 | break; | ||
| 400 | |||
| 401 | case "file": | ||
| 402 | case "protected": | ||
| 403 | access = AccessModifier.File; | ||
| 404 | break; | ||
| 405 | |||
| 406 | case "private": | ||
| 407 | case "fragment": | ||
| 408 | case "section": | ||
| 409 | access = AccessModifier.Section; | ||
| 410 | break; | ||
| 411 | |||
| 412 | default: | ||
| 413 | return null; | ||
| 414 | } | ||
| 415 | |||
| 416 | value = value.Substring(separator + 1).Trim(); | ||
| 417 | } | ||
| 418 | |||
| 419 | if (!Common.IsIdentifier(value)) | ||
| 420 | { | ||
| 421 | this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 422 | return null; | ||
| 423 | } | ||
| 424 | else if (72 < value.Length) | ||
| 425 | { | ||
| 426 | this.Messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 427 | } | ||
| 428 | |||
| 429 | return new Identifier(access, value); | ||
| 430 | } | ||
| 431 | |||
| 432 | public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 433 | { | ||
| 434 | return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute); | ||
| 435 | } | ||
| 436 | |||
| 437 | public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) | ||
| 438 | { | ||
| 439 | return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum); | ||
| 440 | } | ||
| 441 | |||
| 442 | public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards, bool allowRelative) | ||
| 443 | { | ||
| 444 | if (null == attribute) | ||
| 445 | { | ||
| 446 | throw new ArgumentNullException("attribute"); | ||
| 447 | } | ||
| 448 | |||
| 449 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 450 | |||
| 451 | if (!String.IsNullOrEmpty(value)) | ||
| 452 | { | ||
| 453 | if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value)) | ||
| 454 | { | ||
| 455 | if (allowRelative) | ||
| 456 | { | ||
| 457 | this.Messaging.Write(ErrorMessages.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 458 | } | ||
| 459 | else | ||
| 460 | { | ||
| 461 | this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 462 | } | ||
| 463 | } | ||
| 464 | else if (allowRelative) | ||
| 465 | { | ||
| 466 | value = this.GetCanonicalRelativePath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value); | ||
| 467 | } | ||
| 468 | else if (CompilerCore.IsAmbiguousFilename(value)) | ||
| 469 | { | ||
| 470 | this.Messaging.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | return value; | ||
| 475 | } | ||
| 476 | |||
| 477 | public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) | ||
| 478 | { | ||
| 479 | Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing."); | ||
| 480 | |||
| 481 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 482 | |||
| 483 | if (0 < value.Length) | ||
| 484 | { | ||
| 485 | try | ||
| 486 | { | ||
| 487 | var longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat); | ||
| 488 | |||
| 489 | if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue) | ||
| 490 | { | ||
| 491 | this.Messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, longValue)); | ||
| 492 | } | ||
| 493 | else if (minimum > longValue || maximum < longValue) | ||
| 494 | { | ||
| 495 | this.Messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum)); | ||
| 496 | longValue = CompilerConstants.IllegalLong; | ||
| 497 | } | ||
| 498 | |||
| 499 | return longValue; | ||
| 500 | } | ||
| 501 | catch (FormatException) | ||
| 502 | { | ||
| 503 | this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 504 | } | ||
| 505 | catch (OverflowException) | ||
| 506 | { | ||
| 507 | this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 508 | } | ||
| 509 | } | ||
| 510 | |||
| 511 | return CompilerConstants.IllegalLong; | ||
| 512 | } | ||
| 513 | |||
| 514 | public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) | ||
| 515 | { | ||
| 516 | return Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, emptyRule); | ||
| 517 | } | ||
| 518 | |||
| 519 | public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) | ||
| 520 | { | ||
| 521 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 522 | if (String.IsNullOrEmpty(value)) | ||
| 523 | { | ||
| 524 | return null; | ||
| 525 | } | ||
| 526 | |||
| 527 | switch (value) | ||
| 528 | { | ||
| 529 | case "HKCR": | ||
| 530 | return RegistryRootType.ClassesRoot; | ||
| 531 | |||
| 532 | case "HKCU": | ||
| 533 | return RegistryRootType.CurrentUser; | ||
| 534 | |||
| 535 | case "HKLM": | ||
| 536 | return RegistryRootType.LocalMachine; | ||
| 537 | |||
| 538 | case "HKU": | ||
| 539 | return RegistryRootType.Users; | ||
| 540 | |||
| 541 | case "HKMU": | ||
| 542 | if (allowHkmu) | ||
| 543 | { | ||
| 544 | return RegistryRootType.MachineUser; | ||
| 545 | } | ||
| 546 | break; | ||
| 547 | } | ||
| 548 | |||
| 549 | if (allowHkmu) | ||
| 550 | { | ||
| 551 | this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKMU", "HKCR", "HKCU", "HKLM", "HKU")); | ||
| 552 | } | ||
| 553 | else | ||
| 554 | { | ||
| 555 | this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKCR", "HKCU", "HKLM", "HKU")); | ||
| 556 | } | ||
| 557 | |||
| 558 | return RegistryRootType.Unknown; | ||
| 559 | } | ||
| 560 | |||
| 561 | public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 562 | { | ||
| 563 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 564 | |||
| 565 | if (!String.IsNullOrEmpty(value)) | ||
| 566 | { | ||
| 567 | if (Version.TryParse(value, out var version)) | ||
| 568 | { | ||
| 569 | return version.ToString(); | ||
| 570 | } | ||
| 571 | |||
| 572 | // Allow versions to contain binder variables. | ||
| 573 | if (Common.ContainsValidBinderVariable(value)) | ||
| 574 | { | ||
| 575 | return value; | ||
| 576 | } | ||
| 577 | |||
| 578 | this.Messaging.Write(ErrorMessages.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 579 | } | ||
| 580 | |||
| 581 | return null; | ||
| 582 | } | ||
| 583 | |||
| 584 | public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 585 | { | ||
| 586 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 587 | |||
| 588 | switch (value) | ||
| 589 | { | ||
| 590 | case "yes": | ||
| 591 | case "true": | ||
| 592 | return YesNoDefaultType.Yes; | ||
| 593 | |||
| 594 | case "no": | ||
| 595 | case "false": | ||
| 596 | return YesNoDefaultType.No; | ||
| 597 | |||
| 598 | case "default": | ||
| 599 | return YesNoDefaultType.Default; | ||
| 600 | |||
| 601 | default: | ||
| 602 | this.Messaging.Write(ErrorMessages.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 603 | return YesNoDefaultType.IllegalValue; | ||
| 604 | } | ||
| 605 | } | ||
| 606 | |||
| 607 | public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) | ||
| 608 | { | ||
| 609 | var value = this.GetAttributeValue(sourceLineNumbers, attribute); | ||
| 610 | |||
| 611 | switch (value) | ||
| 612 | { | ||
| 613 | case "yes": | ||
| 614 | case "true": | ||
| 615 | return YesNoType.Yes; | ||
| 616 | |||
| 617 | case "no": | ||
| 618 | case "false": | ||
| 619 | return YesNoType.No; | ||
| 620 | |||
| 621 | default: | ||
| 622 | this.Messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); | ||
| 623 | return YesNoType.IllegalValue; | ||
| 624 | } | ||
| 625 | } | ||
| 626 | |||
| 627 | public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) | ||
| 628 | { | ||
| 629 | return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); | ||
| 630 | } | ||
| 631 | |||
| 632 | public SourceLineNumber GetSourceLineNumbers(XElement element) | ||
| 633 | { | ||
| 634 | return Preprocessor.GetSourceLineNumbers(element); | ||
| 635 | } | ||
| 636 | |||
| 637 | public string GetConditionInnerText(XElement element) | ||
| 638 | { | ||
| 639 | var value = Common.GetInnerText(element)?.Trim().Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); | ||
| 640 | |||
| 641 | // Return null for a non-existant condition. | ||
| 642 | return String.IsNullOrEmpty(value) ? null : value; | ||
| 643 | } | ||
| 644 | |||
| 645 | public string GetTrimmedInnerText(XElement element) | ||
| 646 | { | ||
| 647 | var value = Common.GetInnerText(element); | ||
| 648 | return value?.Trim(); | ||
| 649 | } | ||
| 650 | |||
| 651 | public void InnerTextDisallowed(XElement element) | ||
| 652 | { | ||
| 653 | if (element.Nodes().Any(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType)) | ||
| 654 | { | ||
| 655 | var innerText = Common.GetInnerText(element); | ||
| 656 | if (!String.IsNullOrWhiteSpace(innerText)) | ||
| 657 | { | ||
| 658 | var sourceLineNumbers = this.GetSourceLineNumbers(element); | ||
| 659 | this.Messaging.Write(ErrorMessages.IllegalInnerText(sourceLineNumbers, element.Name.LocalName, innerText)); | ||
| 660 | } | ||
| 661 | } | ||
| 662 | } | ||
| 663 | |||
| 664 | public bool IsValidIdentifier(string value) | ||
| 665 | { | ||
| 666 | return Common.IsIdentifier(value); | ||
| 667 | } | ||
| 668 | |||
| 669 | public bool IsValidLocIdentifier(string identifier) | ||
| 670 | { | ||
| 671 | return Common.TryParseWixVariable(identifier, 0, out var parsed) && parsed.Index == 0 && parsed.Length == identifier.Length && parsed.Namespace == "loc"; | ||
| 672 | } | ||
| 673 | |||
| 674 | public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) | ||
| 675 | { | ||
| 676 | return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); | ||
| 677 | } | ||
| 678 | |||
| 679 | public bool IsValidShortFilename(string filename, bool allowWildcards) | ||
| 680 | { | ||
| 681 | return Common.IsValidShortFilename(filename, allowWildcards); | ||
| 682 | } | ||
| 683 | |||
| 684 | public void ParseExtensionAttribute(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement element, XAttribute attribute, IDictionary<string, string> context = null) | ||
| 685 | { | ||
| 686 | // Ignore attributes defined by the W3C because we'll assume they are always right. | ||
| 687 | if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || | ||
| 688 | attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)) | ||
| 689 | { | ||
| 690 | return; | ||
| 691 | } | ||
| 692 | |||
| 693 | if (ParseHelper.TryFindExtension(extensions, attribute.Name.NamespaceName, out var extension)) | ||
| 694 | { | ||
| 695 | extension.ParseAttribute(intermediate, section, element, attribute, context); | ||
| 696 | } | ||
| 697 | else | ||
| 698 | { | ||
| 699 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); | ||
| 700 | this.Messaging.Write(ErrorMessages.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName)); | ||
| 701 | } | ||
| 702 | } | ||
| 703 | |||
| 704 | public void ParseExtensionElement(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context = null) | ||
| 705 | { | ||
| 706 | if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) | ||
| 707 | { | ||
| 708 | extension.ParseElement(intermediate, section, parentElement, element, context); | ||
| 709 | } | ||
| 710 | else | ||
| 711 | { | ||
| 712 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); | ||
| 713 | this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); | ||
| 714 | } | ||
| 715 | } | ||
| 716 | |||
| 717 | public IComponentKeyPath ParsePossibleKeyPathExtensionElement(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context) | ||
| 718 | { | ||
| 719 | IComponentKeyPath keyPath = null; | ||
| 720 | |||
| 721 | if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) | ||
| 722 | { | ||
| 723 | keyPath = extension.ParsePossibleKeyPathElement(intermediate, section, parentElement, element, context); | ||
| 724 | } | ||
| 725 | else | ||
| 726 | { | ||
| 727 | var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); | ||
| 728 | this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); | ||
| 729 | } | ||
| 730 | |||
| 731 | return keyPath; | ||
| 732 | } | ||
| 733 | |||
| 734 | public void ParseForExtensionElements(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement element) | ||
| 735 | { | ||
| 736 | var checkInnerText = false; | ||
| 737 | |||
| 738 | foreach (var child in element.Nodes()) | ||
| 739 | { | ||
| 740 | if (child is XElement childElement) | ||
| 741 | { | ||
| 742 | if (element.Name.Namespace == childElement.Name.Namespace) | ||
| 743 | { | ||
| 744 | this.UnexpectedElement(element, childElement); | ||
| 745 | } | ||
| 746 | else | ||
| 747 | { | ||
| 748 | this.ParseExtensionElement(extensions, intermediate, section, element, childElement); | ||
| 749 | } | ||
| 750 | } | ||
| 751 | else | ||
| 752 | { | ||
| 753 | checkInnerText = true; | ||
| 754 | } | ||
| 755 | } | ||
| 756 | |||
| 757 | if (checkInnerText) | ||
| 758 | { | ||
| 759 | this.InnerTextDisallowed(element); | ||
| 760 | } | ||
| 761 | } | ||
| 762 | |||
| 763 | public WixActionSymbol ScheduleActionSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition, string beforeAction, string afterAction, bool overridable = false) | ||
| 764 | { | ||
| 765 | var actionId = new Identifier(access, sequence, actionName); | ||
| 766 | |||
| 767 | var actionSymbol = section.AddSymbol(new WixActionSymbol(sourceLineNumbers, actionId) | ||
| 768 | { | ||
| 769 | SequenceTable = sequence, | ||
| 770 | Action = actionName, | ||
| 771 | Condition = condition, | ||
| 772 | Before = beforeAction, | ||
| 773 | After = afterAction, | ||
| 774 | Overridable = overridable, | ||
| 775 | }); | ||
| 776 | |||
| 777 | if (null != beforeAction) | ||
| 778 | { | ||
| 779 | if (WindowsInstallerStandard.IsStandardAction(beforeAction)) | ||
| 780 | { | ||
| 781 | this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), beforeAction); | ||
| 782 | } | ||
| 783 | else | ||
| 784 | { | ||
| 785 | this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, beforeAction); | ||
| 786 | } | ||
| 787 | } | ||
| 788 | |||
| 789 | if (null != afterAction) | ||
| 790 | { | ||
| 791 | if (WindowsInstallerStandard.IsStandardAction(afterAction)) | ||
| 792 | { | ||
| 793 | this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), afterAction); | ||
| 794 | } | ||
| 795 | else | ||
| 796 | { | ||
| 797 | this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, afterAction); | ||
| 798 | } | ||
| 799 | } | ||
| 800 | |||
| 801 | return actionSymbol; | ||
| 802 | } | ||
| 803 | |||
| 804 | public void CreateCustomActionReference(SourceLineNumber sourceLineNumbers, IntermediateSection section, string customAction, Platform currentPlatform, CustomActionPlatforms supportedPlatforms) | ||
| 805 | { | ||
| 806 | if (!this.Messaging.EncounteredError) | ||
| 807 | { | ||
| 808 | var suffix = "_X86"; | ||
| 809 | |||
| 810 | switch (currentPlatform) | ||
| 811 | { | ||
| 812 | case Platform.X64: | ||
| 813 | if ((supportedPlatforms & CustomActionPlatforms.X64) == CustomActionPlatforms.X64) | ||
| 814 | { | ||
| 815 | suffix = "_X64"; | ||
| 816 | } | ||
| 817 | break; | ||
| 818 | case Platform.ARM64: | ||
| 819 | if ((supportedPlatforms & CustomActionPlatforms.ARM64) == CustomActionPlatforms.ARM64) | ||
| 820 | { | ||
| 821 | suffix = "_A64"; | ||
| 822 | } | ||
| 823 | break; | ||
| 824 | } | ||
| 825 | |||
| 826 | this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, customAction + suffix); | ||
| 827 | } | ||
| 828 | } | ||
| 829 | |||
| 830 | public void UnexpectedAttribute(XElement element, XAttribute attribute) | ||
| 831 | { | ||
| 832 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); | ||
| 833 | Common.UnexpectedAttribute(this.Messaging, sourceLineNumbers, attribute); | ||
| 834 | } | ||
| 835 | |||
| 836 | public void UnexpectedElement(XElement parentElement, XElement childElement) | ||
| 837 | { | ||
| 838 | var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement); | ||
| 839 | this.Messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName)); | ||
| 840 | } | ||
| 841 | |||
| 842 | private void CreateSymbolDefinitionCreator() | ||
| 843 | { | ||
| 844 | this.Creator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>(); | ||
| 845 | } | ||
| 846 | |||
| 847 | private static bool TryFindExtension(IEnumerable<ICompilerExtension> extensions, XNamespace ns, out ICompilerExtension extension) | ||
| 848 | { | ||
| 849 | extension = null; | ||
| 850 | |||
| 851 | foreach (var ext in extensions) | ||
| 852 | { | ||
| 853 | if (ext.Namespace == ns) | ||
| 854 | { | ||
| 855 | extension = ext; | ||
| 856 | break; | ||
| 857 | } | ||
| 858 | } | ||
| 859 | |||
| 860 | return extension != null; | ||
| 861 | } | ||
| 862 | } | ||
| 863 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs b/src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs new file mode 100644 index 00000000..72be2bcb --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs | |||
| @@ -0,0 +1,118 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.WindowsInstaller; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class PathResolver : IPathResolver | ||
| 14 | { | ||
| 15 | public string GetCanonicalDirectoryPath(Dictionary<string, IResolvedDirectory> directories, Dictionary<string, string> componentIdGenSeeds, string directory, Platform platform) | ||
| 16 | { | ||
| 17 | if (!directories.TryGetValue(directory, out var resolvedDirectory)) | ||
| 18 | { | ||
| 19 | throw new WixException(ErrorMessages.ExpectedDirectory(directory)); | ||
| 20 | } | ||
| 21 | |||
| 22 | if (null == resolvedDirectory.Path) | ||
| 23 | { | ||
| 24 | if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory)) | ||
| 25 | { | ||
| 26 | resolvedDirectory.Path = componentIdGenSeeds[directory]; | ||
| 27 | } | ||
| 28 | else if (WindowsInstallerStandard.IsStandardDirectory(directory)) | ||
| 29 | { | ||
| 30 | resolvedDirectory.Path = WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directory, platform); | ||
| 31 | } | ||
| 32 | else | ||
| 33 | { | ||
| 34 | var name = resolvedDirectory.Name?.ToLowerInvariant(); | ||
| 35 | |||
| 36 | if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) | ||
| 37 | { | ||
| 38 | resolvedDirectory.Path = name; | ||
| 39 | } | ||
| 40 | else | ||
| 41 | { | ||
| 42 | var parentPath = this.GetCanonicalDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, platform); | ||
| 43 | |||
| 44 | if (null != resolvedDirectory.Name) | ||
| 45 | { | ||
| 46 | resolvedDirectory.Path = Path.Combine(parentPath, name); | ||
| 47 | } | ||
| 48 | else | ||
| 49 | { | ||
| 50 | resolvedDirectory.Path = parentPath; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | return resolvedDirectory.Path; | ||
| 57 | } | ||
| 58 | |||
| 59 | public string GetDirectoryPath(Dictionary<string, IResolvedDirectory> directories, string directory) | ||
| 60 | { | ||
| 61 | if (!directories.TryGetValue(directory, out var resolvedDirectory)) | ||
| 62 | { | ||
| 63 | throw new WixException(ErrorMessages.ExpectedDirectory(directory)); | ||
| 64 | } | ||
| 65 | |||
| 66 | if (null == resolvedDirectory.Path) | ||
| 67 | { | ||
| 68 | var name = resolvedDirectory.Name; | ||
| 69 | |||
| 70 | if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) | ||
| 71 | { | ||
| 72 | resolvedDirectory.Path = name; | ||
| 73 | } | ||
| 74 | else | ||
| 75 | { | ||
| 76 | var parentPath = this.GetDirectoryPath(directories, resolvedDirectory.DirectoryParent); | ||
| 77 | |||
| 78 | if (null != resolvedDirectory.Name) | ||
| 79 | { | ||
| 80 | resolvedDirectory.Path = Path.Combine(parentPath, name); | ||
| 81 | } | ||
| 82 | else | ||
| 83 | { | ||
| 84 | resolvedDirectory.Path = parentPath; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | return resolvedDirectory.Path; | ||
| 90 | } | ||
| 91 | |||
| 92 | public string GetFileSourcePath(Dictionary<string, IResolvedDirectory> directories, string directoryId, string fileName, bool compressed, bool useLongName) | ||
| 93 | { | ||
| 94 | var fileSourcePath = Common.GetName(fileName, true, useLongName); | ||
| 95 | |||
| 96 | if (compressed) | ||
| 97 | { | ||
| 98 | // Use just the file name of the file since all uncompressed files must appear | ||
| 99 | // in the root of the image in a compressed package. | ||
| 100 | } | ||
| 101 | else | ||
| 102 | { | ||
| 103 | // Get the relative path of where we want the file to be layed out as specified | ||
| 104 | // in the Directory table. | ||
| 105 | var directoryPath = this.GetDirectoryPath(directories, directoryId); | ||
| 106 | fileSourcePath = Path.Combine(directoryPath, fileSourcePath); | ||
| 107 | } | ||
| 108 | |||
| 109 | // Strip off "SourceDir" if it's still on there. | ||
| 110 | if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal)) | ||
| 111 | { | ||
| 112 | fileSourcePath = fileSourcePath.Substring(10); | ||
| 113 | } | ||
| 114 | |||
| 115 | return fileSourcePath; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs new file mode 100644 index 00000000..b0c87bcf --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs | |||
| @@ -0,0 +1,499 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Text; | ||
| 9 | using System.Xml.Linq; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Extensibility; | ||
| 12 | using WixToolset.Extensibility.Data; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | internal class PreprocessHelper : IPreprocessHelper | ||
| 16 | { | ||
| 17 | private static readonly char[] VariableSplitter = new char[] { '.' }; | ||
| 18 | private static readonly char[] ArgumentSplitter = new char[] { ',' }; | ||
| 19 | |||
| 20 | public PreprocessHelper(IServiceProvider serviceProvider) | ||
| 21 | { | ||
| 22 | this.ServiceProvider = serviceProvider; | ||
| 23 | |||
| 24 | this.Messaging = this.ServiceProvider.GetService<IMessaging>(); | ||
| 25 | } | ||
| 26 | |||
| 27 | private IServiceProvider ServiceProvider { get; } | ||
| 28 | |||
| 29 | private IMessaging Messaging { get; } | ||
| 30 | |||
| 31 | private Dictionary<string, IPreprocessorExtension> ExtensionsByPrefix { get; set; } | ||
| 32 | |||
| 33 | public void AddVariable(IPreprocessContext context, string name, string value) | ||
| 34 | { | ||
| 35 | this.AddVariable(context, name, value, true); | ||
| 36 | } | ||
| 37 | |||
| 38 | public void AddVariable(IPreprocessContext context, string name, string value, bool showWarning) | ||
| 39 | { | ||
| 40 | var currentValue = this.GetVariableValue(context, "var", name); | ||
| 41 | |||
| 42 | if (null == currentValue) | ||
| 43 | { | ||
| 44 | context.Variables.Add(name, value); | ||
| 45 | } | ||
| 46 | else | ||
| 47 | { | ||
| 48 | if (showWarning && value != currentValue) | ||
| 49 | { | ||
| 50 | this.Messaging.Write(WarningMessages.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue)); | ||
| 51 | } | ||
| 52 | |||
| 53 | context.Variables[name] = value; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | public string EvaluateFunction(IPreprocessContext context, string function) | ||
| 58 | { | ||
| 59 | var prefixParts = function.Split(VariableSplitter, 2); | ||
| 60 | |||
| 61 | // Check to make sure there are 2 parts and neither is an empty string. | ||
| 62 | if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length) | ||
| 63 | { | ||
| 64 | throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); | ||
| 65 | } | ||
| 66 | |||
| 67 | var prefix = prefixParts[0]; | ||
| 68 | var functionParts = prefixParts[1].Split(new char[] { '(' }, 2); | ||
| 69 | |||
| 70 | // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren. | ||
| 71 | if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal)) | ||
| 72 | { | ||
| 73 | throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); | ||
| 74 | } | ||
| 75 | |||
| 76 | var functionName = functionParts[0]; | ||
| 77 | |||
| 78 | // Remove the trailing closing paren. | ||
| 79 | var allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1); | ||
| 80 | |||
| 81 | // Parse the arguments and preprocess them. | ||
| 82 | var args = allArgs.Split(ArgumentSplitter); | ||
| 83 | for (var i = 0; i < args.Length; i++) | ||
| 84 | { | ||
| 85 | args[i] = this.PreprocessString(context, args[i].Trim()); | ||
| 86 | } | ||
| 87 | |||
| 88 | var result = this.EvaluateFunction(context, prefix, functionName, args); | ||
| 89 | |||
| 90 | // If the function didn't evaluate, try to evaluate the original value as a variable to support | ||
| 91 | // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. | ||
| 92 | if (result == null) | ||
| 93 | { | ||
| 94 | result = this.GetVariableValue(context, function, true); | ||
| 95 | } | ||
| 96 | |||
| 97 | return result; | ||
| 98 | } | ||
| 99 | |||
| 100 | public string EvaluateFunction(IPreprocessContext context, string prefix, string function, string[] args) | ||
| 101 | { | ||
| 102 | if (String.IsNullOrEmpty(prefix)) | ||
| 103 | { | ||
| 104 | throw new ArgumentNullException("prefix"); | ||
| 105 | } | ||
| 106 | |||
| 107 | if (String.IsNullOrEmpty(function)) | ||
| 108 | { | ||
| 109 | throw new ArgumentNullException("function"); | ||
| 110 | } | ||
| 111 | |||
| 112 | switch (prefix) | ||
| 113 | { | ||
| 114 | case "fun": | ||
| 115 | switch (function) | ||
| 116 | { | ||
| 117 | case "AutoVersion": | ||
| 118 | // Make sure the base version is specified | ||
| 119 | if (args.Length == 0 || String.IsNullOrEmpty(args[0])) | ||
| 120 | { | ||
| 121 | throw new WixException(ErrorMessages.InvalidPreprocessorFunctionAutoVersion(context.CurrentSourceLineNumber)); | ||
| 122 | } | ||
| 123 | |||
| 124 | // Build = days since 1/1/2000; Revision = seconds since midnight / 2 | ||
| 125 | var now = DateTime.UtcNow; | ||
| 126 | var build = now - new DateTime(2000, 1, 1); | ||
| 127 | var revision = now - new DateTime(now.Year, now.Month, now.Day); | ||
| 128 | |||
| 129 | return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2)); | ||
| 130 | |||
| 131 | default: | ||
| 132 | return null; | ||
| 133 | } | ||
| 134 | |||
| 135 | default: | ||
| 136 | var extensionsByPrefix = this.GetExtensionsByPrefix(); | ||
| 137 | if (extensionsByPrefix.TryGetValue(prefix, out var extension)) | ||
| 138 | { | ||
| 139 | try | ||
| 140 | { | ||
| 141 | return extension.EvaluateFunction(prefix, function, args); | ||
| 142 | } | ||
| 143 | catch (Exception e) | ||
| 144 | { | ||
| 145 | throw new WixException(ErrorMessages.PreprocessorExtensionEvaluateFunctionFailed(context.CurrentSourceLineNumber, prefix, function, String.Join(",", args), e.Message)); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | else | ||
| 149 | { | ||
| 150 | return null; | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix) | ||
| 156 | { | ||
| 157 | // Strip the "$(" off the front and the ")" off the back. | ||
| 158 | if (variable.StartsWith("$(", StringComparison.Ordinal)) | ||
| 159 | { | ||
| 160 | variable = variable.Substring(2, variable.Length - 3); | ||
| 161 | } | ||
| 162 | |||
| 163 | var parts = variable.Split(VariableSplitter, 2); | ||
| 164 | |||
| 165 | if (1 == parts.Length) // missing prefix | ||
| 166 | { | ||
| 167 | if (allowMissingPrefix) | ||
| 168 | { | ||
| 169 | return this.GetVariableValue(context, "var", parts[0]); | ||
| 170 | } | ||
| 171 | else | ||
| 172 | { | ||
| 173 | throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); | ||
| 174 | } | ||
| 175 | } | ||
| 176 | else | ||
| 177 | { | ||
| 178 | // check for empty variable name | ||
| 179 | if (0 < parts[1].Length) | ||
| 180 | { | ||
| 181 | string result = this.GetVariableValue(context, parts[0], parts[1]); | ||
| 182 | |||
| 183 | // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix | ||
| 184 | if (null == result && allowMissingPrefix && variable.Contains(".")) | ||
| 185 | { | ||
| 186 | result = this.GetVariableValue(context, "var", variable); | ||
| 187 | } | ||
| 188 | |||
| 189 | return result; | ||
| 190 | } | ||
| 191 | else | ||
| 192 | { | ||
| 193 | throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | public string GetVariableValue(IPreprocessContext context, string prefix, string name) | ||
| 199 | { | ||
| 200 | if (String.IsNullOrEmpty(prefix)) | ||
| 201 | { | ||
| 202 | throw new ArgumentNullException("prefix"); | ||
| 203 | } | ||
| 204 | |||
| 205 | if (String.IsNullOrEmpty(name)) | ||
| 206 | { | ||
| 207 | throw new ArgumentNullException("name"); | ||
| 208 | } | ||
| 209 | |||
| 210 | switch (prefix) | ||
| 211 | { | ||
| 212 | case "env": | ||
| 213 | return Environment.GetEnvironmentVariable(name); | ||
| 214 | |||
| 215 | case "sys": | ||
| 216 | switch (name) | ||
| 217 | { | ||
| 218 | case "CURRENTDIR": | ||
| 219 | return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar); | ||
| 220 | |||
| 221 | case "SOURCEFILEDIR": | ||
| 222 | return String.Concat(Path.GetDirectoryName(context.CurrentSourceLineNumber.FileName), Path.DirectorySeparatorChar); | ||
| 223 | |||
| 224 | case "SOURCEFILEPATH": | ||
| 225 | return context.CurrentSourceLineNumber.FileName; | ||
| 226 | |||
| 227 | case "PLATFORM": | ||
| 228 | this.Messaging.Write(WarningMessages.DeprecatedPreProcVariable(context.CurrentSourceLineNumber, "$(sys.PLATFORM)", "$(sys.BUILDARCH)")); | ||
| 229 | |||
| 230 | goto case "BUILDARCH"; | ||
| 231 | |||
| 232 | case "BUILDARCH": | ||
| 233 | switch (context.Platform) | ||
| 234 | { | ||
| 235 | case Platform.X86: | ||
| 236 | return "x86"; | ||
| 237 | |||
| 238 | case Platform.X64: | ||
| 239 | return "x64"; | ||
| 240 | |||
| 241 | case Platform.ARM64: | ||
| 242 | return "arm64"; | ||
| 243 | |||
| 244 | default: | ||
| 245 | throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", context.Platform.ToString()); | ||
| 246 | } | ||
| 247 | |||
| 248 | case "WIXMAJORVERSION": | ||
| 249 | return ThisAssembly.AssemblyFileVersion.Split('.')[0]; | ||
| 250 | |||
| 251 | case "WIXVERSION": | ||
| 252 | return ThisAssembly.AssemblyFileVersion; | ||
| 253 | |||
| 254 | default: | ||
| 255 | return null; | ||
| 256 | } | ||
| 257 | |||
| 258 | case "var": | ||
| 259 | return context.Variables.TryGetValue(name, out var result) ? result : null; | ||
| 260 | |||
| 261 | default: | ||
| 262 | var extensionsByPrefix = this.GetExtensionsByPrefix(); | ||
| 263 | if (extensionsByPrefix.TryGetValue(prefix, out var extension)) | ||
| 264 | { | ||
| 265 | try | ||
| 266 | { | ||
| 267 | return extension.GetVariableValue(prefix, name); | ||
| 268 | } | ||
| 269 | catch (Exception e) | ||
| 270 | { | ||
| 271 | throw new WixException(ErrorMessages.PreprocessorExtensionGetVariableValueFailed(context.CurrentSourceLineNumber, prefix, name, e.Message)); | ||
| 272 | } | ||
| 273 | } | ||
| 274 | else | ||
| 275 | { | ||
| 276 | return null; | ||
| 277 | } | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | public void PreprocessPragma(IPreprocessContext context, string pragmaName, string args, XContainer parent) | ||
| 282 | { | ||
| 283 | var prefixParts = pragmaName.Split(VariableSplitter, 2); | ||
| 284 | |||
| 285 | // Check to make sure there are 2 parts and neither is an empty string. | ||
| 286 | if (2 != prefixParts.Length) | ||
| 287 | { | ||
| 288 | throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); | ||
| 289 | } | ||
| 290 | |||
| 291 | var prefix = prefixParts[0]; | ||
| 292 | var pragma = prefixParts[1]; | ||
| 293 | |||
| 294 | if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma)) | ||
| 295 | { | ||
| 296 | throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); | ||
| 297 | } | ||
| 298 | |||
| 299 | switch (prefix) | ||
| 300 | { | ||
| 301 | case "wix": | ||
| 302 | switch (pragma) | ||
| 303 | { | ||
| 304 | // Add any core defined pragmas here | ||
| 305 | default: | ||
| 306 | this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); | ||
| 307 | break; | ||
| 308 | } | ||
| 309 | break; | ||
| 310 | |||
| 311 | default: | ||
| 312 | var extensionsByPrefix = this.GetExtensionsByPrefix(); | ||
| 313 | if (extensionsByPrefix.TryGetValue(prefix, out var extension)) | ||
| 314 | { | ||
| 315 | if (!extension.ProcessPragma(prefix, pragma, args, parent)) | ||
| 316 | { | ||
| 317 | this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | break; | ||
| 321 | } | ||
| 322 | } | ||
| 323 | |||
| 324 | public string PreprocessString(IPreprocessContext context, string value) | ||
| 325 | { | ||
| 326 | var sb = new StringBuilder(); | ||
| 327 | var currentPosition = 0; | ||
| 328 | var end = 0; | ||
| 329 | |||
| 330 | while (-1 != (currentPosition = value.IndexOf('$', end))) | ||
| 331 | { | ||
| 332 | if (end < currentPosition) | ||
| 333 | { | ||
| 334 | sb.Append(value, end, currentPosition - end); | ||
| 335 | } | ||
| 336 | |||
| 337 | end = currentPosition + 1; | ||
| 338 | |||
| 339 | var remainder = value.Substring(end); | ||
| 340 | if (remainder.StartsWith("$", StringComparison.Ordinal)) | ||
| 341 | { | ||
| 342 | sb.Append("$"); | ||
| 343 | end++; | ||
| 344 | } | ||
| 345 | else if (remainder.StartsWith("(loc.", StringComparison.Ordinal)) | ||
| 346 | { | ||
| 347 | currentPosition = remainder.IndexOf(')'); | ||
| 348 | if (-1 == currentPosition) | ||
| 349 | { | ||
| 350 | this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); | ||
| 351 | break; | ||
| 352 | } | ||
| 353 | |||
| 354 | sb.Append("$"); // just put the resource reference back as was | ||
| 355 | sb.Append(remainder, 0, currentPosition + 1); | ||
| 356 | |||
| 357 | end += currentPosition + 1; | ||
| 358 | } | ||
| 359 | else if (remainder.StartsWith("(", StringComparison.Ordinal)) | ||
| 360 | { | ||
| 361 | var openParenCount = 1; | ||
| 362 | var closingParenCount = 0; | ||
| 363 | var isFunction = false; | ||
| 364 | var foundClosingParen = false; | ||
| 365 | |||
| 366 | // find the closing paren | ||
| 367 | int closingParenPosition; | ||
| 368 | for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++) | ||
| 369 | { | ||
| 370 | switch (remainder[closingParenPosition]) | ||
| 371 | { | ||
| 372 | case '(': | ||
| 373 | openParenCount++; | ||
| 374 | isFunction = true; | ||
| 375 | break; | ||
| 376 | |||
| 377 | case ')': | ||
| 378 | closingParenCount++; | ||
| 379 | break; | ||
| 380 | } | ||
| 381 | |||
| 382 | if (openParenCount == closingParenCount) | ||
| 383 | { | ||
| 384 | foundClosingParen = true; | ||
| 385 | break; | ||
| 386 | } | ||
| 387 | } | ||
| 388 | |||
| 389 | // Environment variables may contain parens so if it looks | ||
| 390 | // like a function, check to see if the environment variable | ||
| 391 | // prefix was explicitly provided. | ||
| 392 | if (isFunction && remainder.StartsWith("(env.", StringComparison.Ordinal)) | ||
| 393 | { | ||
| 394 | isFunction = false; | ||
| 395 | } | ||
| 396 | |||
| 397 | // move the currentPosition to the closing paren | ||
| 398 | currentPosition += closingParenPosition; | ||
| 399 | |||
| 400 | if (!foundClosingParen) | ||
| 401 | { | ||
| 402 | if (isFunction) | ||
| 403 | { | ||
| 404 | this.Messaging.Write(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, remainder)); | ||
| 405 | break; | ||
| 406 | } | ||
| 407 | else | ||
| 408 | { | ||
| 409 | this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); | ||
| 410 | break; | ||
| 411 | } | ||
| 412 | } | ||
| 413 | |||
| 414 | var subString = remainder.Substring(1, closingParenPosition - 1); | ||
| 415 | string result = null; | ||
| 416 | if (isFunction) | ||
| 417 | { | ||
| 418 | result = this.EvaluateFunction(context, subString); | ||
| 419 | } | ||
| 420 | else | ||
| 421 | { | ||
| 422 | result = this.GetVariableValue(context, subString, true); | ||
| 423 | } | ||
| 424 | |||
| 425 | if (null == result) | ||
| 426 | { | ||
| 427 | if (isFunction) | ||
| 428 | { | ||
| 429 | this.Messaging.Write(ErrorMessages.UndefinedPreprocessorFunction(context.CurrentSourceLineNumber, subString)); | ||
| 430 | break; | ||
| 431 | } | ||
| 432 | else | ||
| 433 | { | ||
| 434 | this.Messaging.Write(ErrorMessages.UndefinedPreprocessorVariable(context.CurrentSourceLineNumber, subString)); | ||
| 435 | break; | ||
| 436 | } | ||
| 437 | } | ||
| 438 | else | ||
| 439 | { | ||
| 440 | if (!isFunction) | ||
| 441 | { | ||
| 442 | //this.OnResolvedVariable(new ResolvedVariableEventArgs(context.CurrentSourceLineNumber, subString, result)); | ||
| 443 | } | ||
| 444 | } | ||
| 445 | |||
| 446 | sb.Append(result); | ||
| 447 | end += closingParenPosition + 1; | ||
| 448 | } | ||
| 449 | else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing | ||
| 450 | { | ||
| 451 | sb.Append('$'); | ||
| 452 | } | ||
| 453 | } | ||
| 454 | |||
| 455 | if (end < value.Length) | ||
| 456 | { | ||
| 457 | sb.Append(value.Substring(end)); | ||
| 458 | } | ||
| 459 | |||
| 460 | return sb.ToString(); | ||
| 461 | } | ||
| 462 | |||
| 463 | public void RemoveVariable(IPreprocessContext context, string name) | ||
| 464 | { | ||
| 465 | if (!context.Variables.Remove(name)) | ||
| 466 | { | ||
| 467 | this.Messaging.Write(ErrorMessages.CannotReundefineVariable(context.CurrentSourceLineNumber, name)); | ||
| 468 | } | ||
| 469 | } | ||
| 470 | |||
| 471 | private Dictionary<string, IPreprocessorExtension> GetExtensionsByPrefix() | ||
| 472 | { | ||
| 473 | if (this.ExtensionsByPrefix == null) | ||
| 474 | { | ||
| 475 | this.ExtensionsByPrefix = new Dictionary<string, IPreprocessorExtension>(); | ||
| 476 | |||
| 477 | var extensionManager = this.ServiceProvider.GetService<IExtensionManager>(); | ||
| 478 | |||
| 479 | var extensions = extensionManager.GetServices<IPreprocessorExtension>(); | ||
| 480 | |||
| 481 | foreach (var extension in extensions) | ||
| 482 | { | ||
| 483 | if (null != extension.Prefixes) | ||
| 484 | { | ||
| 485 | foreach (string prefix in extension.Prefixes) | ||
| 486 | { | ||
| 487 | if (!this.ExtensionsByPrefix.ContainsKey(prefix)) | ||
| 488 | { | ||
| 489 | this.ExtensionsByPrefix.Add(prefix, extension); | ||
| 490 | } | ||
| 491 | } | ||
| 492 | } | ||
| 493 | } | ||
| 494 | } | ||
| 495 | |||
| 496 | return this.ExtensionsByPrefix; | ||
| 497 | } | ||
| 498 | } | ||
| 499 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs new file mode 100644 index 00000000..cc8acfdd --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using WixToolset.Extensibility.Data; | ||
| 6 | |||
| 7 | internal class ResolvedDirectory : IResolvedDirectory | ||
| 8 | { | ||
| 9 | public string DirectoryParent { get; set; } | ||
| 10 | |||
| 11 | public string Name { get; set; } | ||
| 12 | |||
| 13 | public string Path { get; set; } | ||
| 14 | } | ||
| 15 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs b/src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs new file mode 100644 index 00000000..a2486130 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Extensibility; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | internal class SymbolDefinitionCreator : ISymbolDefinitionCreator | ||
| 12 | { | ||
| 13 | public SymbolDefinitionCreator(IServiceProvider serviceProvider) | ||
| 14 | { | ||
| 15 | this.ServiceProvider = serviceProvider; | ||
| 16 | } | ||
| 17 | |||
| 18 | private IServiceProvider ServiceProvider { get; } | ||
| 19 | |||
| 20 | private IEnumerable<IExtensionData> ExtensionData { get; set; } | ||
| 21 | |||
| 22 | private Dictionary<string, IntermediateSymbolDefinition> CustomDefinitionByName { get; } = new Dictionary<string, IntermediateSymbolDefinition>(); | ||
| 23 | |||
| 24 | public void AddCustomSymbolDefinition(IntermediateSymbolDefinition definition) | ||
| 25 | { | ||
| 26 | if (!this.CustomDefinitionByName.TryGetValue(definition.Name, out var existing) || definition.Revision > existing.Revision) | ||
| 27 | { | ||
| 28 | this.CustomDefinitionByName[definition.Name] = definition; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) | ||
| 33 | { | ||
| 34 | // First, look in the built-ins. | ||
| 35 | symbolDefinition = SymbolDefinitions.ByName(name); | ||
| 36 | |||
| 37 | if (symbolDefinition == null) | ||
| 38 | { | ||
| 39 | if (this.ExtensionData == null) | ||
| 40 | { | ||
| 41 | this.LoadExtensionData(); | ||
| 42 | } | ||
| 43 | |||
| 44 | // Second, look in the extensions. | ||
| 45 | foreach (var data in this.ExtensionData) | ||
| 46 | { | ||
| 47 | if (data.TryGetSymbolDefinitionByName(name, out symbolDefinition)) | ||
| 48 | { | ||
| 49 | break; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | // Finally, look in the custom symbol definitions provided during an intermediate load. | ||
| 54 | if (symbolDefinition == null) | ||
| 55 | { | ||
| 56 | this.CustomDefinitionByName.TryGetValue(name, out symbolDefinition); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | return symbolDefinition != null; | ||
| 61 | } | ||
| 62 | |||
| 63 | private void LoadExtensionData() | ||
| 64 | { | ||
| 65 | var extensionManager = this.ServiceProvider.GetService<IExtensionManager>(); | ||
| 66 | |||
| 67 | this.ExtensionData = extensionManager.GetServices<IExtensionData>(); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs b/src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs new file mode 100644 index 00000000..028cddbf --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | internal class TrackedFile : ITrackedFile | ||
| 9 | { | ||
| 10 | public TrackedFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers) | ||
| 11 | { | ||
| 12 | this.Path = path; | ||
| 13 | this.Type = type; | ||
| 14 | this.SourceLineNumbers = sourceLineNumbers; | ||
| 15 | this.Clean = (type == TrackedFileType.Intermediate || type == TrackedFileType.Final); | ||
| 16 | } | ||
| 17 | |||
| 18 | public bool Clean { get; set; } | ||
| 19 | |||
| 20 | public string Path { get; set; } | ||
| 21 | |||
| 22 | public SourceLineNumber SourceLineNumbers { get; set; } | ||
| 23 | |||
| 24 | public TrackedFileType Type { get; set; } | ||
| 25 | } | ||
| 26 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs b/src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs new file mode 100644 index 00000000..ad9eea26 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.ExtensibilityServices | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Net; | ||
| 7 | using System.Security.Cryptography; | ||
| 8 | using System.Text; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Implementation of RFC 4122 - A Universally Unique Identifier (UUID) URN Namespace. | ||
| 12 | /// </summary> | ||
| 13 | internal static class Uuid | ||
| 14 | { | ||
| 15 | /// <summary> | ||
| 16 | /// Creates a version 3 name-based UUID. | ||
| 17 | /// </summary> | ||
| 18 | /// <param name="namespaceGuid">The namespace UUID.</param> | ||
| 19 | /// <param name="value">The value.</param> | ||
| 20 | /// <returns>The UUID for the given namespace and value.</returns> | ||
| 21 | public static Guid NewUuid(Guid namespaceGuid, string value) | ||
| 22 | { | ||
| 23 | byte[] namespaceBytes = namespaceGuid.ToByteArray(); | ||
| 24 | short uuidVersion = (short)0x5000; | ||
| 25 | |||
| 26 | // get the fields of the guid which are in host byte ordering | ||
| 27 | int timeLow = BitConverter.ToInt32(namespaceBytes, 0); | ||
| 28 | short timeMid = BitConverter.ToInt16(namespaceBytes, 4); | ||
| 29 | short timeHiAndVersion = BitConverter.ToInt16(namespaceBytes, 6); | ||
| 30 | |||
| 31 | // convert to network byte ordering | ||
| 32 | timeLow = IPAddress.HostToNetworkOrder(timeLow); | ||
| 33 | timeMid = IPAddress.HostToNetworkOrder(timeMid); | ||
| 34 | timeHiAndVersion = IPAddress.HostToNetworkOrder(timeHiAndVersion); | ||
| 35 | |||
| 36 | // get the bytes from the value | ||
| 37 | byte[] valueBytes = Encoding.Unicode.GetBytes(value); | ||
| 38 | |||
| 39 | // fill-in the hash input buffer | ||
| 40 | byte[] buffer = new byte[namespaceBytes.Length + valueBytes.Length]; | ||
| 41 | Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, buffer, 0, 4); | ||
| 42 | Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, buffer, 4, 2); | ||
| 43 | Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, buffer, 6, 2); | ||
| 44 | Buffer.BlockCopy(namespaceBytes, 8, buffer, 8, 8); | ||
| 45 | Buffer.BlockCopy(valueBytes, 0, buffer, 16, valueBytes.Length); | ||
| 46 | |||
| 47 | // perform the appropriate hash of the namespace and value | ||
| 48 | byte[] hash; | ||
| 49 | using (SHA1 sha1 = SHA1.Create()) | ||
| 50 | { | ||
| 51 | hash = sha1.ComputeHash(buffer); | ||
| 52 | } | ||
| 53 | |||
| 54 | // get the fields of the hash which are in network byte ordering | ||
| 55 | timeLow = BitConverter.ToInt32(hash, 0); | ||
| 56 | timeMid = BitConverter.ToInt16(hash, 4); | ||
| 57 | timeHiAndVersion = BitConverter.ToInt16(hash, 6); | ||
| 58 | |||
| 59 | // convert to network byte ordering | ||
| 60 | timeLow = IPAddress.NetworkToHostOrder(timeLow); | ||
| 61 | timeMid = IPAddress.NetworkToHostOrder(timeMid); | ||
| 62 | timeHiAndVersion = IPAddress.NetworkToHostOrder(timeHiAndVersion); | ||
| 63 | |||
| 64 | // set the version and variant bits | ||
| 65 | timeHiAndVersion &= 0x0FFF; | ||
| 66 | timeHiAndVersion += uuidVersion; | ||
| 67 | hash[8] &= 0x3F; | ||
| 68 | hash[8] |= 0x80; | ||
| 69 | |||
| 70 | // put back the converted values into a 128-bit value | ||
| 71 | byte[] guidBits = new byte[16]; | ||
| 72 | Buffer.BlockCopy(hash, 0, guidBits, 0, 16); | ||
| 73 | |||
| 74 | Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, guidBits, 0, 4); | ||
| 75 | Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, guidBits, 4, 2); | ||
| 76 | Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, guidBits, 6, 2); | ||
| 77 | |||
| 78 | return new Guid(guidBits); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs b/src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs new file mode 100644 index 00000000..56300400 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs | |||
| @@ -0,0 +1,124 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | using System.Resources; | ||
| 4 | |||
| 5 | [assembly: NeutralResourcesLanguage("en-US")] | ||
| 6 | |||
| 7 | namespace WixToolset.Core.ExtensibilityServices | ||
| 8 | { | ||
| 9 | using System; | ||
| 10 | using System.Diagnostics; | ||
| 11 | using System.IO; | ||
| 12 | using System.Reflection; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Branding strings. | ||
| 17 | /// </summary> | ||
| 18 | internal class WixBranding : IWixBranding | ||
| 19 | { | ||
| 20 | /// <summary> | ||
| 21 | /// News URL for the distribution. | ||
| 22 | /// </summary> | ||
| 23 | public static string NewsUrl = "http://wixtoolset.org/news/"; | ||
| 24 | |||
| 25 | /// <summary> | ||
| 26 | /// Short product name for the distribution. | ||
| 27 | /// </summary> | ||
| 28 | public static string ShortProduct = "WiX Toolset"; | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Support URL for the distribution. | ||
| 32 | /// </summary> | ||
| 33 | public static string SupportUrl = "http://wixtoolset.org/"; | ||
| 34 | |||
| 35 | /// <summary> | ||
| 36 | /// Telemetry URL format for the distribution. | ||
| 37 | /// </summary> | ||
| 38 | public static string TelemetryUrlFormat = "http://wixtoolset.org/telemetry/v{0}/?r={1}"; | ||
| 39 | |||
| 40 | /// <summary> | ||
| 41 | /// VS Extensions Landing page Url for the distribution. | ||
| 42 | /// </summary> | ||
| 43 | public static string VSExtensionsLandingUrl = "http://wixtoolset.org/releases/"; | ||
| 44 | |||
| 45 | public string GetCreatingApplication() | ||
| 46 | { | ||
| 47 | return this.ReplacePlaceholders("[AssemblyProduct] ([FileVersion])"); | ||
| 48 | } | ||
| 49 | |||
| 50 | public string ReplacePlaceholders(string original, Assembly assembly = null) | ||
| 51 | { | ||
| 52 | if (assembly == null) | ||
| 53 | { | ||
| 54 | assembly = typeof(WixBranding).Assembly; | ||
| 55 | } | ||
| 56 | |||
| 57 | var commonVersionPath = Path.Combine(Path.GetDirectoryName(typeof(WixBranding).Assembly.Location), "wixver.dll"); | ||
| 58 | if (File.Exists(commonVersionPath)) | ||
| 59 | { | ||
| 60 | var commonFileVersion = FileVersionInfo.GetVersionInfo(commonVersionPath); | ||
| 61 | |||
| 62 | original = original.Replace("[FileCopyright]", commonFileVersion.LegalCopyright); | ||
| 63 | original = original.Replace("[FileVersion]", commonFileVersion.FileVersion); | ||
| 64 | } | ||
| 65 | |||
| 66 | var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location); | ||
| 67 | |||
| 68 | original = original.Replace("[FileComments]", fileVersion.Comments); | ||
| 69 | original = original.Replace("[FileCopyright]", fileVersion.LegalCopyright); | ||
| 70 | original = original.Replace("[FileProductName]", fileVersion.ProductName); | ||
| 71 | original = original.Replace("[FileVersion]", fileVersion.FileVersion); | ||
| 72 | |||
| 73 | if (original.Contains("[FileVersionMajorMinor]")) | ||
| 74 | { | ||
| 75 | var version = new Version(fileVersion.FileVersion); | ||
| 76 | original = original.Replace("[FileVersionMajorMinor]", String.Concat(version.Major, ".", version.Minor)); | ||
| 77 | } | ||
| 78 | |||
| 79 | if (TryGetAttribute(assembly, out AssemblyCompanyAttribute company)) | ||
| 80 | { | ||
| 81 | original = original.Replace("[AssemblyCompany]", company.Company); | ||
| 82 | } | ||
| 83 | |||
| 84 | if (TryGetAttribute(assembly, out AssemblyCopyrightAttribute copyright)) | ||
| 85 | { | ||
| 86 | original = original.Replace("[AssemblyCopyright]", copyright.Copyright); | ||
| 87 | } | ||
| 88 | |||
| 89 | if (TryGetAttribute(assembly, out AssemblyDescriptionAttribute description)) | ||
| 90 | { | ||
| 91 | original = original.Replace("[AssemblyDescription]", description.Description); | ||
| 92 | } | ||
| 93 | |||
| 94 | if (TryGetAttribute(assembly, out AssemblyProductAttribute product)) | ||
| 95 | { | ||
| 96 | original = original.Replace("[AssemblyProduct]", product.Product); | ||
| 97 | } | ||
| 98 | |||
| 99 | if (TryGetAttribute(assembly, out AssemblyTitleAttribute title)) | ||
| 100 | { | ||
| 101 | original = original.Replace("[AssemblyTitle]", title.Title); | ||
| 102 | } | ||
| 103 | |||
| 104 | original = original.Replace("[NewsUrl]", NewsUrl); | ||
| 105 | original = original.Replace("[ShortProduct]", ShortProduct); | ||
| 106 | original = original.Replace("[SupportUrl]", SupportUrl); | ||
| 107 | |||
| 108 | return original; | ||
| 109 | } | ||
| 110 | |||
| 111 | private static bool TryGetAttribute<T>(Assembly assembly, out T attribute) where T : Attribute | ||
| 112 | { | ||
| 113 | attribute = null; | ||
| 114 | |||
| 115 | var customAttributes = assembly.GetCustomAttributes(typeof(T), false); | ||
| 116 | if (null != customAttributes && 0 < customAttributes.Length) | ||
| 117 | { | ||
| 118 | attribute = customAttributes[0] as T; | ||
| 119 | } | ||
| 120 | |||
| 121 | return null != attribute; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | } | ||
diff --git a/src/wix/WixToolset.Core/IBinder.cs b/src/wix/WixToolset.Core/IBinder.cs new file mode 100644 index 00000000..a1b66f42 --- /dev/null +++ b/src/wix/WixToolset.Core/IBinder.cs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Extensibility.Data; | ||
| 6 | |||
| 7 | #pragma warning disable 1591 // TODO: add documentation | ||
| 8 | public interface IBinder | ||
| 9 | { | ||
| 10 | IBindResult Bind(IBindContext context); | ||
| 11 | } | ||
| 12 | } | ||
diff --git a/src/wix/WixToolset.Core/ICompiler.cs b/src/wix/WixToolset.Core/ICompiler.cs new file mode 100644 index 00000000..0aae579a --- /dev/null +++ b/src/wix/WixToolset.Core/ICompiler.cs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | #pragma warning disable 1591 // TODO: add documentation | ||
| 9 | public interface ICompiler | ||
| 10 | { | ||
| 11 | Intermediate Compile(ICompileContext context); | ||
| 12 | } | ||
| 13 | } | ||
diff --git a/src/wix/WixToolset.Core/IDecompiler.cs b/src/wix/WixToolset.Core/IDecompiler.cs new file mode 100644 index 00000000..74ec26de --- /dev/null +++ b/src/wix/WixToolset.Core/IDecompiler.cs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Extensibility.Data; | ||
| 6 | |||
| 7 | #pragma warning disable 1591 // TODO: add documentation | ||
| 8 | public interface IDecompiler | ||
| 9 | { | ||
| 10 | IDecompileResult Decompile(IDecompileContext context); | ||
| 11 | } | ||
| 12 | } | ||
diff --git a/src/wix/WixToolset.Core/ILayoutCreator.cs b/src/wix/WixToolset.Core/ILayoutCreator.cs new file mode 100644 index 00000000..cdff2a78 --- /dev/null +++ b/src/wix/WixToolset.Core/ILayoutCreator.cs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Extensibility.Data; | ||
| 6 | |||
| 7 | #pragma warning disable 1591 // TODO: add documentation | ||
| 8 | public interface ILayoutCreator | ||
| 9 | { | ||
| 10 | void Layout(ILayoutContext context); | ||
| 11 | } | ||
| 12 | } | ||
diff --git a/src/wix/WixToolset.Core/ILibrarian.cs b/src/wix/WixToolset.Core/ILibrarian.cs new file mode 100644 index 00000000..0fcedea5 --- /dev/null +++ b/src/wix/WixToolset.Core/ILibrarian.cs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | #pragma warning disable 1591 // TODO: add documentation | ||
| 9 | public interface ILibrarian | ||
| 10 | { | ||
| 11 | Intermediate Combine(ILibraryContext context); | ||
| 12 | } | ||
| 13 | } | ||
diff --git a/src/wix/WixToolset.Core/ILinker.cs b/src/wix/WixToolset.Core/ILinker.cs new file mode 100644 index 00000000..11cc2c87 --- /dev/null +++ b/src/wix/WixToolset.Core/ILinker.cs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | #pragma warning disable 1591 // TODO: add documentation | ||
| 9 | public interface ILinker | ||
| 10 | { | ||
| 11 | Intermediate Link(ILinkContext context); | ||
| 12 | } | ||
| 13 | } | ||
diff --git a/src/wix/WixToolset.Core/ILocalizationParser.cs b/src/wix/WixToolset.Core/ILocalizationParser.cs new file mode 100644 index 00000000..0e70aa0e --- /dev/null +++ b/src/wix/WixToolset.Core/ILocalizationParser.cs | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System.Xml.Linq; | ||
| 6 | using WixToolset.Data; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Parses localization source files. | ||
| 10 | /// </summary> | ||
| 11 | public interface ILocalizationParser | ||
| 12 | { | ||
| 13 | /// <summary> | ||
| 14 | /// Loads a localization file from a path on disk. | ||
| 15 | /// </summary> | ||
| 16 | /// <param name="path">Path to localization file saved on disk.</param> | ||
| 17 | /// <returns>Returns the loaded localization file.</returns> | ||
| 18 | Localization ParseLocalization(string path); | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Loads a localization file from memory. | ||
| 22 | /// </summary> | ||
| 23 | /// <param name="document">Document to parse as localization file.</param> | ||
| 24 | /// <returns>Returns the loaded localization file.</returns> | ||
| 25 | Localization ParseLocalization(XDocument document); | ||
| 26 | } | ||
| 27 | } | ||
diff --git a/src/wix/WixToolset.Core/IPreprocessor.cs b/src/wix/WixToolset.Core/IPreprocessor.cs new file mode 100644 index 00000000..f6ed5fed --- /dev/null +++ b/src/wix/WixToolset.Core/IPreprocessor.cs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System.Xml; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | #pragma warning disable 1591 // TODO: add documentation, move into Extensibility | ||
| 9 | public interface IPreprocessor | ||
| 10 | { | ||
| 11 | IPreprocessResult Preprocess(IPreprocessContext context); | ||
| 12 | |||
| 13 | IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader); | ||
| 14 | } | ||
| 15 | } | ||
diff --git a/src/wix/WixToolset.Core/IResolver.cs b/src/wix/WixToolset.Core/IResolver.cs new file mode 100644 index 00000000..db25edbe --- /dev/null +++ b/src/wix/WixToolset.Core/IResolver.cs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Extensibility.Data; | ||
| 6 | |||
| 7 | /// <summary> | ||
| 8 | /// Resolves localization and bind variables. | ||
| 9 | /// </summary> | ||
| 10 | public interface IResolver | ||
| 11 | { | ||
| 12 | /// <summary> | ||
| 13 | /// Resolve localization and bind variables. | ||
| 14 | /// </summary> | ||
| 15 | /// <param name="context">Resolve context.</param> | ||
| 16 | /// <returns>Resolve result.</returns> | ||
| 17 | IResolveResult Resolve(IResolveContext context); | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/src/wix/WixToolset.Core/IUnbinder.cs b/src/wix/WixToolset.Core/IUnbinder.cs new file mode 100644 index 00000000..2b4daaa5 --- /dev/null +++ b/src/wix/WixToolset.Core/IUnbinder.cs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | #pragma warning disable 1591 // TODO: add documentation, move into Extensibility | ||
| 8 | public interface IUnbinder | ||
| 9 | { | ||
| 10 | Intermediate Unbind(string file, OutputType outputType, string exportBasePath); | ||
| 11 | } | ||
| 12 | } | ||
diff --git a/src/wix/WixToolset.Core/IncludedFile.cs b/src/wix/WixToolset.Core/IncludedFile.cs new file mode 100644 index 00000000..25d51191 --- /dev/null +++ b/src/wix/WixToolset.Core/IncludedFile.cs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | internal class IncludedFile : IIncludedFile | ||
| 9 | { | ||
| 10 | public string Path { get; set; } | ||
| 11 | |||
| 12 | public SourceLineNumber SourceLineNumbers { get; set; } | ||
| 13 | } | ||
| 14 | } | ||
diff --git a/src/wix/WixToolset.Core/IncribeContext.cs b/src/wix/WixToolset.Core/IncribeContext.cs new file mode 100644 index 00000000..9d7055ab --- /dev/null +++ b/src/wix/WixToolset.Core/IncribeContext.cs | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | using WixToolset.Extensibility.Services; | ||
| 8 | |||
| 9 | internal class InscribeContext : IInscribeContext | ||
| 10 | { | ||
| 11 | public InscribeContext(IServiceProvider serviceProvider) | ||
| 12 | { | ||
| 13 | this.ServiceProvider = serviceProvider; | ||
| 14 | } | ||
| 15 | |||
| 16 | public IServiceProvider ServiceProvider { get; } | ||
| 17 | |||
| 18 | public string IntermediateFolder { get; set; } | ||
| 19 | |||
| 20 | public string InputFilePath { get; set; } | ||
| 21 | |||
| 22 | public string SignedEngineFile { get; set; } | ||
| 23 | |||
| 24 | public string OutputFile { get; set; } | ||
| 25 | } | ||
| 26 | } | ||
diff --git a/src/wix/WixToolset.Core/LayoutContext.cs b/src/wix/WixToolset.Core/LayoutContext.cs new file mode 100644 index 00000000..4b8c7b99 --- /dev/null +++ b/src/wix/WixToolset.Core/LayoutContext.cs | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Threading; | ||
| 8 | using WixToolset.Extensibility; | ||
| 9 | using WixToolset.Extensibility.Data; | ||
| 10 | |||
| 11 | internal class LayoutContext : ILayoutContext | ||
| 12 | { | ||
| 13 | internal LayoutContext(IServiceProvider serviceProvider) | ||
| 14 | { | ||
| 15 | this.ServiceProvider = serviceProvider; | ||
| 16 | } | ||
| 17 | |||
| 18 | public IServiceProvider ServiceProvider { get; } | ||
| 19 | |||
| 20 | public IReadOnlyCollection<ILayoutExtension> Extensions { get; set; } | ||
| 21 | |||
| 22 | public IReadOnlyCollection<IFileSystemExtension> FileSystemExtensions { get; set; } | ||
| 23 | |||
| 24 | public IReadOnlyCollection<IFileTransfer> FileTransfers { get; set; } | ||
| 25 | |||
| 26 | public IReadOnlyCollection<ITrackedFile> TrackedFiles { get; set; } | ||
| 27 | |||
| 28 | public string IntermediateFolder { get; set; } | ||
| 29 | |||
| 30 | public string ContentsFile { get; set; } | ||
| 31 | |||
| 32 | public string OutputsFile { get; set; } | ||
| 33 | |||
| 34 | public string BuiltOutputsFile { get; set; } | ||
| 35 | |||
| 36 | public bool ResetAcls { get; set; } | ||
| 37 | |||
| 38 | public CancellationToken CancellationToken { get; set; } | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/wix/WixToolset.Core/LayoutCreator.cs b/src/wix/WixToolset.Core/LayoutCreator.cs new file mode 100644 index 00000000..0c5aaf63 --- /dev/null +++ b/src/wix/WixToolset.Core/LayoutCreator.cs | |||
| @@ -0,0 +1,223 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Core.Bind; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Layout for the WiX toolset. | ||
| 16 | /// </summary> | ||
| 17 | internal class LayoutCreator : ILayoutCreator | ||
| 18 | { | ||
| 19 | internal LayoutCreator(IServiceProvider serviceProvider) | ||
| 20 | { | ||
| 21 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 22 | } | ||
| 23 | |||
| 24 | private IMessaging Messaging { get; } | ||
| 25 | |||
| 26 | public void Layout(ILayoutContext context) | ||
| 27 | { | ||
| 28 | // Pre-layout. | ||
| 29 | // | ||
| 30 | foreach (var extension in context.Extensions) | ||
| 31 | { | ||
| 32 | extension.PreLayout(context); | ||
| 33 | } | ||
| 34 | |||
| 35 | try | ||
| 36 | { | ||
| 37 | // Final step in binding that transfers (moves/copies) all files generated into the appropriate | ||
| 38 | // location in the source image. | ||
| 39 | if (context.FileTransfers?.Any() == true) | ||
| 40 | { | ||
| 41 | this.Messaging.Write(VerboseMessages.LayingOutMedia()); | ||
| 42 | |||
| 43 | var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.ResetAcls); | ||
| 44 | command.Execute(); | ||
| 45 | } | ||
| 46 | |||
| 47 | if (context.TrackedFiles != null) | ||
| 48 | { | ||
| 49 | this.CleanTempFiles(context.IntermediateFolder, context.TrackedFiles); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | finally | ||
| 53 | { | ||
| 54 | if (context.TrackedFiles != null) | ||
| 55 | { | ||
| 56 | if (!String.IsNullOrEmpty(context.ContentsFile)) | ||
| 57 | { | ||
| 58 | this.CreateContentsFile(context.ContentsFile, context.TrackedFiles); | ||
| 59 | } | ||
| 60 | |||
| 61 | if (!String.IsNullOrEmpty(context.OutputsFile)) | ||
| 62 | { | ||
| 63 | this.CreateOutputsFile(context.OutputsFile, context.TrackedFiles); | ||
| 64 | } | ||
| 65 | |||
| 66 | if (!String.IsNullOrEmpty(context.BuiltOutputsFile)) | ||
| 67 | { | ||
| 68 | this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.TrackedFiles); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | // Post-layout. | ||
| 74 | foreach (var extension in context.Extensions) | ||
| 75 | { | ||
| 76 | extension.PostLayout(); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | /// <summary> | ||
| 81 | /// Writes the paths to the content files to a text file. | ||
| 82 | /// </summary> | ||
| 83 | /// <param name="path">Path to write file.</param> | ||
| 84 | /// <param name="trackedFiles">Collection of paths to content files that will be written to file.</param> | ||
| 85 | private void CreateContentsFile(string path, IEnumerable<ITrackedFile> trackedFiles) | ||
| 86 | { | ||
| 87 | var uniqueInputFilePaths = new SortedSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Input).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); | ||
| 88 | |||
| 89 | if (!uniqueInputFilePaths.Any()) | ||
| 90 | { | ||
| 91 | return; | ||
| 92 | } | ||
| 93 | |||
| 94 | var directory = Path.GetDirectoryName(path); | ||
| 95 | Directory.CreateDirectory(directory); | ||
| 96 | |||
| 97 | using (var contents = new StreamWriter(path, false)) | ||
| 98 | { | ||
| 99 | foreach (var inputPath in uniqueInputFilePaths) | ||
| 100 | { | ||
| 101 | contents.WriteLine(inputPath); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | /// <summary> | ||
| 107 | /// Writes the paths to the output files to a text file. | ||
| 108 | /// </summary> | ||
| 109 | /// <param name="path">Path to write file.</param> | ||
| 110 | /// <param name="trackedFiles">Collection of files that were transferred to the output directory.</param> | ||
| 111 | private void CreateOutputsFile(string path, IEnumerable<ITrackedFile> trackedFiles) | ||
| 112 | { | ||
| 113 | var uniqueOutputPaths = new SortedSet<string>(trackedFiles.Where(t => t.Clean).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); | ||
| 114 | |||
| 115 | if (!uniqueOutputPaths.Any()) | ||
| 116 | { | ||
| 117 | return; | ||
| 118 | } | ||
| 119 | |||
| 120 | var directory = Path.GetDirectoryName(path); | ||
| 121 | Directory.CreateDirectory(directory); | ||
| 122 | |||
| 123 | using (var outputs = new StreamWriter(path, false)) | ||
| 124 | { | ||
| 125 | //// Don't list files where the source is the same as the destination since | ||
| 126 | //// that might be the only place the file exists. The outputs file is often | ||
| 127 | //// used to delete stuff and losing the original source would be bad. | ||
| 128 | //var uniqueOutputPaths = new SortedSet<string>(fileTransfers.Where(ft => !ft.Redundant).Select(ft => ft.Destination), StringComparer.OrdinalIgnoreCase); | ||
| 129 | |||
| 130 | foreach (var outputPath in uniqueOutputPaths) | ||
| 131 | { | ||
| 132 | outputs.WriteLine(outputPath); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | /// <summary> | ||
| 138 | /// Writes the paths to the built output files to a text file. | ||
| 139 | /// </summary> | ||
| 140 | /// <param name="path">Path to write file.</param> | ||
| 141 | /// <param name="trackedFiles">Collection of files that were transferred to the output directory.</param> | ||
| 142 | private void CreateBuiltOutputsFile(string path, IEnumerable<ITrackedFile> trackedFiles) | ||
| 143 | { | ||
| 144 | var uniqueBuiltPaths = new SortedSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Final).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); | ||
| 145 | |||
| 146 | if (!uniqueBuiltPaths.Any()) | ||
| 147 | { | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | |||
| 151 | var directory = Path.GetDirectoryName(path); | ||
| 152 | Directory.CreateDirectory(directory); | ||
| 153 | |||
| 154 | using (var outputs = new StreamWriter(path, false)) | ||
| 155 | { | ||
| 156 | foreach (var builtPath in uniqueBuiltPaths) | ||
| 157 | { | ||
| 158 | outputs.WriteLine(builtPath); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | private void CleanTempFiles(string intermediateFolder, IEnumerable<ITrackedFile> trackedFiles) | ||
| 164 | { | ||
| 165 | var uniqueTempPaths = new SortedSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Temporary).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); | ||
| 166 | |||
| 167 | if (!uniqueTempPaths.Any()) | ||
| 168 | { | ||
| 169 | return; | ||
| 170 | } | ||
| 171 | |||
| 172 | var uniqueFolders = new SortedSet<string>(StringComparer.OrdinalIgnoreCase) | ||
| 173 | { | ||
| 174 | intermediateFolder | ||
| 175 | }; | ||
| 176 | |||
| 177 | // Clean up temp files. | ||
| 178 | foreach (var tempPath in uniqueTempPaths) | ||
| 179 | { | ||
| 180 | try | ||
| 181 | { | ||
| 182 | this.SplitUniqueFolders(intermediateFolder, tempPath, uniqueFolders); | ||
| 183 | |||
| 184 | File.Delete(tempPath); | ||
| 185 | } | ||
| 186 | catch // delete is best effort. | ||
| 187 | { | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | // Clean up empty temp folders. | ||
| 192 | foreach (var folder in uniqueFolders.Reverse()) | ||
| 193 | { | ||
| 194 | try | ||
| 195 | { | ||
| 196 | Directory.Delete(folder); | ||
| 197 | } | ||
| 198 | catch // delete is best effort. | ||
| 199 | { | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | private void SplitUniqueFolders(string intermediateFolder, string tempPath, SortedSet<string> uniqueFolders) | ||
| 205 | { | ||
| 206 | if (tempPath.StartsWith(intermediateFolder, StringComparison.OrdinalIgnoreCase)) | ||
| 207 | { | ||
| 208 | var folder = Path.GetDirectoryName(tempPath).Substring(intermediateFolder.Length); | ||
| 209 | |||
| 210 | var parts = folder.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); | ||
| 211 | |||
| 212 | folder = intermediateFolder; | ||
| 213 | |||
| 214 | foreach (var part in parts) | ||
| 215 | { | ||
| 216 | folder = Path.Combine(folder, part); | ||
| 217 | |||
| 218 | uniqueFolders.Add(folder); | ||
| 219 | } | ||
| 220 | } | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
diff --git a/src/wix/WixToolset.Core/Librarian.cs b/src/wix/WixToolset.Core/Librarian.cs new file mode 100644 index 00000000..1dd1b44d --- /dev/null +++ b/src/wix/WixToolset.Core/Librarian.cs | |||
| @@ -0,0 +1,135 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Core.Bind; | ||
| 9 | using WixToolset.Core.Link; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Extensibility.Data; | ||
| 12 | using WixToolset.Extensibility.Services; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Core librarian tool. | ||
| 16 | /// </summary> | ||
| 17 | internal class Librarian : ILibrarian | ||
| 18 | { | ||
| 19 | internal Librarian(IServiceProvider serviceProvider) | ||
| 20 | { | ||
| 21 | this.ServiceProvider = serviceProvider; | ||
| 22 | |||
| 23 | this.Messaging = this.ServiceProvider.GetService<IMessaging>(); | ||
| 24 | } | ||
| 25 | |||
| 26 | private IServiceProvider ServiceProvider { get; } | ||
| 27 | |||
| 28 | private IMessaging Messaging { get; } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Create a library by combining several intermediates (objects). | ||
| 32 | /// </summary> | ||
| 33 | /// <returns>Returns the new library.</returns> | ||
| 34 | public Intermediate Combine(ILibraryContext context) | ||
| 35 | { | ||
| 36 | if (String.IsNullOrEmpty(context.LibraryId)) | ||
| 37 | { | ||
| 38 | context.LibraryId = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('=').Replace('+', '.').Replace('/', '_'); | ||
| 39 | } | ||
| 40 | |||
| 41 | foreach (var extension in context.Extensions) | ||
| 42 | { | ||
| 43 | extension.PreCombine(context); | ||
| 44 | } | ||
| 45 | |||
| 46 | Intermediate library = null; | ||
| 47 | try | ||
| 48 | { | ||
| 49 | var sections = context.Intermediates.SelectMany(i => i.Sections).ToList(); | ||
| 50 | |||
| 51 | var collate = new CollateLocalizationsCommand(this.Messaging, context.Localizations); | ||
| 52 | var localizationsByCulture = collate.Execute(); | ||
| 53 | |||
| 54 | if (this.Messaging.EncounteredError) | ||
| 55 | { | ||
| 56 | return null; | ||
| 57 | } | ||
| 58 | |||
| 59 | this.ResolveFilePathsToEmbed(context, sections); | ||
| 60 | |||
| 61 | foreach (var section in sections) | ||
| 62 | { | ||
| 63 | section.AssignToLibrary(context.LibraryId); | ||
| 64 | } | ||
| 65 | |||
| 66 | library = new Intermediate(context.LibraryId, IntermediateLevels.Compiled, sections, localizationsByCulture); | ||
| 67 | |||
| 68 | library.UpdateLevel(IntermediateLevels.Combined); | ||
| 69 | |||
| 70 | this.Validate(library); | ||
| 71 | } | ||
| 72 | finally | ||
| 73 | { | ||
| 74 | foreach (var extension in context.Extensions) | ||
| 75 | { | ||
| 76 | extension.PostCombine(library); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | return this.Messaging.EncounteredError ? null : library; | ||
| 81 | } | ||
| 82 | |||
| 83 | private void ResolveFilePathsToEmbed(ILibraryContext context, IEnumerable<IntermediateSection> sections) | ||
| 84 | { | ||
| 85 | // Resolve paths to files that are to be embedded in the library. | ||
| 86 | if (context.BindFiles) | ||
| 87 | { | ||
| 88 | var variableResolver = this.ServiceProvider.GetService<IVariableResolver>(); | ||
| 89 | |||
| 90 | var fileResolver = new FileResolver(context.BindPaths, context.Extensions); | ||
| 91 | |||
| 92 | foreach (var symbol in sections.SelectMany(s => s.Symbols)) | ||
| 93 | { | ||
| 94 | foreach (var field in symbol.Fields.Where(f => f?.Type == IntermediateFieldType.Path)) | ||
| 95 | { | ||
| 96 | var pathField = field.AsPath(); | ||
| 97 | |||
| 98 | if (pathField != null && !String.IsNullOrEmpty(pathField.Path)) | ||
| 99 | { | ||
| 100 | var resolution = variableResolver.ResolveVariables(symbol.SourceLineNumbers, pathField.Path); | ||
| 101 | |||
| 102 | var file = fileResolver.Resolve(symbol.SourceLineNumbers, symbol.Definition, resolution.Value); | ||
| 103 | |||
| 104 | if (!String.IsNullOrEmpty(file)) | ||
| 105 | { | ||
| 106 | // File was successfully resolved so track the embedded index as the embedded file index. | ||
| 107 | field.Set(new IntermediateFieldPathValue { Embed = true, Path = file }); | ||
| 108 | } | ||
| 109 | else | ||
| 110 | { | ||
| 111 | this.Messaging.Write(ErrorMessages.FileNotFound(symbol.SourceLineNumbers, pathField.Path, symbol.Definition.Name)); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | private void Validate(Intermediate library) | ||
| 120 | { | ||
| 121 | var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, library.Sections, OutputType.Library); | ||
| 122 | find.Execute(); | ||
| 123 | |||
| 124 | // TODO: Consider bringing this sort of verification back. | ||
| 125 | // foreach (Section section in library.Sections) | ||
| 126 | // { | ||
| 127 | // ResolveReferencesCommand resolve = new ResolveReferencesCommand(find.EntrySection, find.Symbols); | ||
| 128 | // resolve.Execute(); | ||
| 129 | // | ||
| 130 | // ReportDuplicateResolvedSymbolErrorsCommand reportDupes = new ReportDuplicateResolvedSymbolErrorsCommand(find.SymbolsWithDuplicates, resolve.ResolvedSections); | ||
| 131 | // reportDupes.Execute(); | ||
| 132 | // } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | } | ||
diff --git a/src/wix/WixToolset.Core/LibraryContext.cs b/src/wix/WixToolset.Core/LibraryContext.cs new file mode 100644 index 00000000..e701cadf --- /dev/null +++ b/src/wix/WixToolset.Core/LibraryContext.cs | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Threading; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class LibraryContext : ILibraryContext | ||
| 14 | { | ||
| 15 | internal LibraryContext(IServiceProvider serviceProvider) | ||
| 16 | { | ||
| 17 | this.ServiceProvider = serviceProvider; | ||
| 18 | } | ||
| 19 | |||
| 20 | public IServiceProvider ServiceProvider { get; } | ||
| 21 | |||
| 22 | public IMessaging Messaging { get; set; } | ||
| 23 | |||
| 24 | public bool BindFiles { get; set; } | ||
| 25 | |||
| 26 | public IReadOnlyCollection<IBindPath> BindPaths { get; set; } | ||
| 27 | |||
| 28 | public IReadOnlyCollection<ILibrarianExtension> Extensions { get; set; } | ||
| 29 | |||
| 30 | public string LibraryId { get; set; } | ||
| 31 | |||
| 32 | public IReadOnlyCollection<Localization> Localizations { get; set; } | ||
| 33 | |||
| 34 | public IReadOnlyCollection<Intermediate> Intermediates { get; set; } | ||
| 35 | |||
| 36 | public CancellationToken CancellationToken { get; set; } | ||
| 37 | } | ||
| 38 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs b/src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs new file mode 100644 index 00000000..d5c69838 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | internal class CollateLocalizationsCommand | ||
| 12 | { | ||
| 13 | public CollateLocalizationsCommand(IMessaging messaging, IEnumerable<Localization> localizations) | ||
| 14 | { | ||
| 15 | this.Messaging = messaging; | ||
| 16 | this.Localizations = localizations; | ||
| 17 | } | ||
| 18 | |||
| 19 | private IMessaging Messaging { get; } | ||
| 20 | |||
| 21 | private IEnumerable<Localization> Localizations { get; } | ||
| 22 | |||
| 23 | public Dictionary<string, Localization> Execute() | ||
| 24 | { | ||
| 25 | var localizationsByCulture = new Dictionary<string, Localization>(StringComparer.OrdinalIgnoreCase); | ||
| 26 | |||
| 27 | foreach (var localization in this.Localizations) | ||
| 28 | { | ||
| 29 | if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture)) | ||
| 30 | { | ||
| 31 | var merged = this.Merge(existingCulture, localization); | ||
| 32 | localizationsByCulture[localization.Culture] = merged; | ||
| 33 | } | ||
| 34 | else | ||
| 35 | { | ||
| 36 | localizationsByCulture.Add(localization.Culture, localization); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | return localizationsByCulture; | ||
| 41 | } | ||
| 42 | |||
| 43 | private Localization Merge(Localization existingLocalization, Localization localization) | ||
| 44 | { | ||
| 45 | var variables = existingLocalization.Variables.ToDictionary(v => v.Id); | ||
| 46 | var controls = existingLocalization.LocalizedControls.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); | ||
| 47 | |||
| 48 | foreach (var newVariable in localization.Variables) | ||
| 49 | { | ||
| 50 | if (!variables.TryGetValue(newVariable.Id, out var existingVariable) || (existingVariable.Overridable && !newVariable.Overridable)) | ||
| 51 | { | ||
| 52 | variables[newVariable.Id] = newVariable; | ||
| 53 | } | ||
| 54 | else if (!newVariable.Overridable) | ||
| 55 | { | ||
| 56 | this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(newVariable.SourceLineNumbers, newVariable.Id)); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | foreach (var localizedControl in localization.LocalizedControls) | ||
| 61 | { | ||
| 62 | if (!controls.ContainsKey(localizedControl.Key)) | ||
| 63 | { | ||
| 64 | controls.Add(localizedControl.Key, localizedControl.Value); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | return new Localization(existingLocalization.Codepage ?? localization.Codepage, existingLocalization.SummaryInformationCodepage ?? localization.SummaryInformationCodepage, existingLocalization.Culture, variables, controls); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/ConnectToFeature.cs b/src/wix/WixToolset.Core/Link/ConnectToFeature.cs new file mode 100644 index 00000000..e9a739a1 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToFeature.cs | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Data; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Object that connects things (components/modules) to features. | ||
| 10 | /// </summary> | ||
| 11 | internal class ConnectToFeature | ||
| 12 | { | ||
| 13 | /// <summary> | ||
| 14 | /// Creates a new connect to feature. | ||
| 15 | /// </summary> | ||
| 16 | /// <param name="section">Section this connect belongs to.</param> | ||
| 17 | /// <param name="childId">Id of the child.</param> | ||
| 18 | /// <param name="primaryFeature">Sets the primary feature for the connection.</param> | ||
| 19 | /// <param name="explicitPrimaryFeature">Sets if this is explicit primary.</param> | ||
| 20 | public ConnectToFeature(IntermediateSection section, string childId, string primaryFeature, bool explicitPrimaryFeature) | ||
| 21 | { | ||
| 22 | this.Section = section; | ||
| 23 | this.ChildId = childId; | ||
| 24 | |||
| 25 | this.PrimaryFeature = primaryFeature; | ||
| 26 | this.IsExplicitPrimaryFeature = explicitPrimaryFeature; | ||
| 27 | } | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// Gets the section. | ||
| 31 | /// </summary> | ||
| 32 | /// <value>Section.</value> | ||
| 33 | public IntermediateSection Section { get; } | ||
| 34 | |||
| 35 | /// <summary> | ||
| 36 | /// Gets the child identifier. | ||
| 37 | /// </summary> | ||
| 38 | /// <value>The child identifier.</value> | ||
| 39 | public string ChildId { get; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets or sets if the flag for if the primary feature was set explicitly. | ||
| 43 | /// </summary> | ||
| 44 | /// <value>The flag for if the primary feature was set explicitly.</value> | ||
| 45 | public bool IsExplicitPrimaryFeature { get; set; } | ||
| 46 | |||
| 47 | /// <summary> | ||
| 48 | /// Gets or sets the primary feature. | ||
| 49 | /// </summary> | ||
| 50 | /// <value>The primary feature.</value> | ||
| 51 | public string PrimaryFeature { get; set; } | ||
| 52 | |||
| 53 | /// <summary> | ||
| 54 | /// Gets the features connected to. | ||
| 55 | /// </summary> | ||
| 56 | /// <value>Features connected to.</value> | ||
| 57 | public List<string> ConnectFeatures { get; } = new List<string>(); | ||
| 58 | } | ||
| 59 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs b/src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs new file mode 100644 index 00000000..b7874527 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Hash collection of connect to feature objects. | ||
| 10 | /// </summary> | ||
| 11 | internal class ConnectToFeatureCollection : ICollection | ||
| 12 | { | ||
| 13 | private Hashtable collection; | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Instantiate a new ConnectToFeatureCollection class. | ||
| 17 | /// </summary> | ||
| 18 | public ConnectToFeatureCollection() | ||
| 19 | { | ||
| 20 | this.collection = new Hashtable(); | ||
| 21 | } | ||
| 22 | |||
| 23 | /// <summary> | ||
| 24 | /// Gets the number of items in the collection. | ||
| 25 | /// </summary> | ||
| 26 | /// <value>Number of items in collection.</value> | ||
| 27 | public int Count | ||
| 28 | { | ||
| 29 | get { return this.collection.Count; } | ||
| 30 | } | ||
| 31 | |||
| 32 | /// <summary> | ||
| 33 | /// Gets if the collection has been synchronized. | ||
| 34 | /// </summary> | ||
| 35 | /// <value>True if the collection has been synchronized.</value> | ||
| 36 | public bool IsSynchronized | ||
| 37 | { | ||
| 38 | get { return this.collection.IsSynchronized; } | ||
| 39 | } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets the object used to synchronize the collection. | ||
| 43 | /// </summary> | ||
| 44 | /// <value>Oject used the synchronize the collection.</value> | ||
| 45 | public object SyncRoot | ||
| 46 | { | ||
| 47 | get { return this.collection.SyncRoot; } | ||
| 48 | } | ||
| 49 | |||
| 50 | /// <summary> | ||
| 51 | /// Gets a feature connection by child id. | ||
| 52 | /// </summary> | ||
| 53 | /// <param name="childId">Identifier of child to locate.</param> | ||
| 54 | public ConnectToFeature this[string childId] | ||
| 55 | { | ||
| 56 | get { return (ConnectToFeature)this.collection[childId]; } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// <summary> | ||
| 60 | /// Adds a feature connection to the collection. | ||
| 61 | /// </summary> | ||
| 62 | /// <param name="connection">Feature connection to add.</param> | ||
| 63 | public void Add(ConnectToFeature connection) | ||
| 64 | { | ||
| 65 | if (null == connection) | ||
| 66 | { | ||
| 67 | throw new ArgumentNullException("connection"); | ||
| 68 | } | ||
| 69 | |||
| 70 | this.collection.Add(connection.ChildId, connection); | ||
| 71 | } | ||
| 72 | |||
| 73 | /// <summary> | ||
| 74 | /// Copies the collection into an array. | ||
| 75 | /// </summary> | ||
| 76 | /// <param name="array">Array to copy the collection into.</param> | ||
| 77 | /// <param name="index">Index to start copying from.</param> | ||
| 78 | public void CopyTo(System.Array array, int index) | ||
| 79 | { | ||
| 80 | this.collection.CopyTo(array, index); | ||
| 81 | } | ||
| 82 | |||
| 83 | /// <summary> | ||
| 84 | /// Gets enumerator for the collection. | ||
| 85 | /// </summary> | ||
| 86 | /// <returns>Enumerator for the collection.</returns> | ||
| 87 | public IEnumerator GetEnumerator() | ||
| 88 | { | ||
| 89 | return this.collection.Values.GetEnumerator(); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/ConnectToModule.cs b/src/wix/WixToolset.Core/Link/ConnectToModule.cs new file mode 100644 index 00000000..4380e12c --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToModule.cs | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | /// <summary> | ||
| 6 | /// Object that connects things to modules. | ||
| 7 | /// </summary> | ||
| 8 | internal class ConnectToModule | ||
| 9 | { | ||
| 10 | private string childId; | ||
| 11 | private string module; | ||
| 12 | private string moduleLanguage; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Creates a new connect to module. | ||
| 16 | /// </summary> | ||
| 17 | /// <param name="childId">Id of the child.</param> | ||
| 18 | /// <param name="module">Id of the module.</param> | ||
| 19 | /// <param name="moduleLanguage">Language of the module.</param> | ||
| 20 | public ConnectToModule(string childId, string module, string moduleLanguage) | ||
| 21 | { | ||
| 22 | this.childId = childId; | ||
| 23 | this.module = module; | ||
| 24 | this.moduleLanguage = moduleLanguage; | ||
| 25 | } | ||
| 26 | |||
| 27 | /// <summary> | ||
| 28 | /// Gets the id of the child. | ||
| 29 | /// </summary> | ||
| 30 | /// <value>Child identifier.</value> | ||
| 31 | public string ChildId | ||
| 32 | { | ||
| 33 | get { return this.childId; } | ||
| 34 | } | ||
| 35 | |||
| 36 | /// <summary> | ||
| 37 | /// Gets the id of the module. | ||
| 38 | /// </summary> | ||
| 39 | /// <value>The id of the module.</value> | ||
| 40 | public string Module | ||
| 41 | { | ||
| 42 | get { return this.module; } | ||
| 43 | } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Gets the language of the module. | ||
| 47 | /// </summary> | ||
| 48 | /// <value>The language of the module.</value> | ||
| 49 | public string ModuleLanguage | ||
| 50 | { | ||
| 51 | get { return this.moduleLanguage; } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs b/src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs new file mode 100644 index 00000000..e0f96ffb --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Hash collection of connect to module objects. | ||
| 10 | /// </summary> | ||
| 11 | internal class ConnectToModuleCollection : ICollection | ||
| 12 | { | ||
| 13 | private Hashtable collection; | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Instantiate a new ConnectToModuleCollection class. | ||
| 17 | /// </summary> | ||
| 18 | public ConnectToModuleCollection() | ||
| 19 | { | ||
| 20 | this.collection = new Hashtable(); | ||
| 21 | } | ||
| 22 | |||
| 23 | /// <summary> | ||
| 24 | /// Gets the number of elements actually contained in the ConnectToModuleCollection. | ||
| 25 | /// </summary> | ||
| 26 | /// <value>The number of elements actually contained in the ConnectToModuleCollection.</value> | ||
| 27 | public int Count | ||
| 28 | { | ||
| 29 | get { return this.collection.Count; } | ||
| 30 | } | ||
| 31 | |||
| 32 | /// <summary> | ||
| 33 | /// Gets a value indicating whether access to the ConnectToModuleCollection is synchronized (thread-safe). | ||
| 34 | /// </summary> | ||
| 35 | /// <value>true if access to the ConnectToModuleCollection is synchronized (thread-safe); otherwise, false. The default is false.</value> | ||
| 36 | public bool IsSynchronized | ||
| 37 | { | ||
| 38 | get { return this.collection.IsSynchronized; } | ||
| 39 | } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets an object that can be used to synchronize access to the ConnectToModuleCollection. | ||
| 43 | /// </summary> | ||
| 44 | /// <value>An object that can be used to synchronize access to the ConnectToModuleCollection.</value> | ||
| 45 | public object SyncRoot | ||
| 46 | { | ||
| 47 | get { return this.collection.SyncRoot; } | ||
| 48 | } | ||
| 49 | |||
| 50 | /// <summary> | ||
| 51 | /// Gets a module connection by child id. | ||
| 52 | /// </summary> | ||
| 53 | /// <param name="childId">Identifier of child to locate.</param> | ||
| 54 | public ConnectToModule this[string childId] | ||
| 55 | { | ||
| 56 | get { return (ConnectToModule)this.collection[childId]; } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// <summary> | ||
| 60 | /// Adds a module connection to the collection. | ||
| 61 | /// </summary> | ||
| 62 | /// <param name="connection">Module connection to add.</param> | ||
| 63 | public void Add(ConnectToModule connection) | ||
| 64 | { | ||
| 65 | if (null == connection) | ||
| 66 | { | ||
| 67 | throw new ArgumentNullException("connection"); | ||
| 68 | } | ||
| 69 | |||
| 70 | this.collection.Add(connection.ChildId, connection); | ||
| 71 | } | ||
| 72 | |||
| 73 | /// <summary> | ||
| 74 | /// Copies the entire ConnectToModuleCollection to a compatible one-dimensional Array, starting at the specified index of the target array. | ||
| 75 | /// </summary> | ||
| 76 | /// <param name="array">The one-dimensional Array that is the destination of the elements copied from this ConnectToModuleCollection. The Array must have zero-based indexing.</param> | ||
| 77 | /// <param name="index">The zero-based index in array at which copying begins.</param> | ||
| 78 | public void CopyTo(System.Array array, int index) | ||
| 79 | { | ||
| 80 | this.collection.Keys.CopyTo(array, index); | ||
| 81 | } | ||
| 82 | |||
| 83 | /// <summary> | ||
| 84 | /// Returns an enumerator for the entire ConnectToModuleCollection. | ||
| 85 | /// </summary> | ||
| 86 | /// <returns>An IEnumerator for the entire ConnectToModuleCollection.</returns> | ||
| 87 | public IEnumerator GetEnumerator() | ||
| 88 | { | ||
| 89 | return this.collection.Keys.GetEnumerator(); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs new file mode 100644 index 00000000..5d6cc831 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs | |||
| @@ -0,0 +1,119 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | internal class FindEntrySectionAndLoadSymbolsCommand | ||
| 12 | { | ||
| 13 | public FindEntrySectionAndLoadSymbolsCommand(IMessaging messaging, IEnumerable<IntermediateSection> sections, OutputType expectedOutpuType) | ||
| 14 | { | ||
| 15 | this.Messaging = messaging; | ||
| 16 | this.Sections = sections; | ||
| 17 | this.ExpectedOutputType = expectedOutpuType; | ||
| 18 | } | ||
| 19 | |||
| 20 | private IMessaging Messaging { get; } | ||
| 21 | |||
| 22 | private IEnumerable<IntermediateSection> Sections { get; } | ||
| 23 | |||
| 24 | private OutputType ExpectedOutputType { get; } | ||
| 25 | |||
| 26 | /// <summary> | ||
| 27 | /// Gets the located entry section after the command is executed. | ||
| 28 | /// </summary> | ||
| 29 | public IntermediateSection EntrySection { get; private set; } | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Gets the collection of loaded symbols. | ||
| 33 | /// </summary> | ||
| 34 | public IDictionary<string, SymbolWithSection> SymbolsByName { get; private set; } | ||
| 35 | |||
| 36 | /// <summary> | ||
| 37 | /// Gets the collection of possibly conflicting symbols. | ||
| 38 | /// </summary> | ||
| 39 | public IEnumerable<SymbolWithSection> PossibleConflicts { get; private set; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets the collection of redundant symbols that should not be included | ||
| 43 | /// in the final output. | ||
| 44 | /// </summary> | ||
| 45 | public ISet<IntermediateSymbol> RedundantSymbols { get; private set; } | ||
| 46 | |||
| 47 | public void Execute() | ||
| 48 | { | ||
| 49 | var symbolsByName = new Dictionary<string, SymbolWithSection>(); | ||
| 50 | var possibleConflicts = new HashSet<SymbolWithSection>(); | ||
| 51 | var redundantSymbols = new HashSet<IntermediateSymbol>(); | ||
| 52 | |||
| 53 | if (!Enum.TryParse(this.ExpectedOutputType.ToString(), out SectionType expectedEntrySectionType)) | ||
| 54 | { | ||
| 55 | expectedEntrySectionType = SectionType.Unknown; | ||
| 56 | } | ||
| 57 | |||
| 58 | foreach (var section in this.Sections) | ||
| 59 | { | ||
| 60 | // Try to find the one and only entry section. | ||
| 61 | if (SectionType.Product == section.Type || SectionType.Module == section.Type || SectionType.PatchCreation == section.Type || SectionType.Patch == section.Type || SectionType.Bundle == section.Type) | ||
| 62 | { | ||
| 63 | // TODO: remove this? | ||
| 64 | //if (SectionType.Unknown != expectedEntrySectionType && section.Type != expectedEntrySectionType) | ||
| 65 | //{ | ||
| 66 | // string outputExtension = Output.GetExtension(this.ExpectedOutputType); | ||
| 67 | // this.Messaging.Write(WixWarnings.UnexpectedEntrySection(section.SourceLineNumbers, section.Type.ToString(), expectedEntrySectionType.ToString(), outputExtension)); | ||
| 68 | //} | ||
| 69 | |||
| 70 | if (null == this.EntrySection) | ||
| 71 | { | ||
| 72 | this.EntrySection = section; | ||
| 73 | } | ||
| 74 | else | ||
| 75 | { | ||
| 76 | this.Messaging.Write(ErrorMessages.MultipleEntrySections(this.EntrySection.Symbols.FirstOrDefault()?.SourceLineNumbers, this.EntrySection.Id, section.Id)); | ||
| 77 | this.Messaging.Write(ErrorMessages.MultipleEntrySections2(section.Symbols.FirstOrDefault()?.SourceLineNumbers)); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | // Load all the symbols from the section's tables that create symbols. | ||
| 82 | foreach (var symbol in section.Symbols.Where(t => t.Id != null)) | ||
| 83 | { | ||
| 84 | var symbolWithSection = new SymbolWithSection(section, symbol); | ||
| 85 | |||
| 86 | if (!symbolsByName.TryGetValue(symbolWithSection.Name, out var existingSymbol)) | ||
| 87 | { | ||
| 88 | symbolsByName.Add(symbolWithSection.Name, symbolWithSection); | ||
| 89 | } | ||
| 90 | else // uh-oh, duplicate symbols. | ||
| 91 | { | ||
| 92 | // If the duplicate symbols are both private directories, there is a chance that they | ||
| 93 | // point to identical symbols. Identical directory symbols are redundant and will not cause | ||
| 94 | // conflicts. | ||
| 95 | if (AccessModifier.Section == existingSymbol.Access && AccessModifier.Section == symbolWithSection.Access && | ||
| 96 | SymbolDefinitionType.Directory == existingSymbol.Symbol.Definition.Type && existingSymbol.Symbol.IsIdentical(symbolWithSection.Symbol)) | ||
| 97 | { | ||
| 98 | // Ensure identical symbol's symbol is marked redundant to ensure (should the symbol be | ||
| 99 | // referenced into the final output) it will not add duplicate primary keys during | ||
| 100 | // the .IDT importing. | ||
| 101 | existingSymbol.AddRedundant(symbolWithSection); | ||
| 102 | redundantSymbols.Add(symbolWithSection.Symbol); | ||
| 103 | } | ||
| 104 | else | ||
| 105 | { | ||
| 106 | symbolWithSection.AddPossibleConflict(existingSymbol); | ||
| 107 | existingSymbol.AddPossibleConflict(symbolWithSection); | ||
| 108 | possibleConflicts.Add(symbolWithSection); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | this.SymbolsByName = symbolsByName; | ||
| 115 | this.PossibleConflicts = possibleConflicts; | ||
| 116 | this.RedundantSymbols = redundantSymbols; | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs b/src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs new file mode 100644 index 00000000..16593c7d --- /dev/null +++ b/src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs | |||
| @@ -0,0 +1,194 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Burn; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class FlattenAndProcessBundleTablesCommand | ||
| 14 | { | ||
| 15 | public FlattenAndProcessBundleTablesCommand(IntermediateSection entrySection, IMessaging messaging) | ||
| 16 | { | ||
| 17 | this.EntrySection = entrySection; | ||
| 18 | this.Messaging = messaging; | ||
| 19 | } | ||
| 20 | |||
| 21 | private IntermediateSection EntrySection { get; } | ||
| 22 | |||
| 23 | private IMessaging Messaging { get; } | ||
| 24 | |||
| 25 | public void Execute() | ||
| 26 | { | ||
| 27 | this.FlattenBundleTables(); | ||
| 28 | |||
| 29 | if (this.Messaging.EncounteredError) | ||
| 30 | { | ||
| 31 | return; | ||
| 32 | } | ||
| 33 | |||
| 34 | this.ProcessBundleComplexReferences(); | ||
| 35 | } | ||
| 36 | |||
| 37 | /// <summary> | ||
| 38 | /// Flattens the tables used in a Bundle. | ||
| 39 | /// </summary> | ||
| 40 | private void FlattenBundleTables() | ||
| 41 | { | ||
| 42 | // We need to flatten the nested PayloadGroups and PackageGroups under | ||
| 43 | // UX, Chain, and any Containers. When we're done, the WixGroups table | ||
| 44 | // will hold Payloads under UX, ChainPackages (references?) under Chain, | ||
| 45 | // and ContainerPackages/Payloads under any authored Containers. | ||
| 46 | var groups = new WixGroupingOrdering(this.EntrySection, this.Messaging); | ||
| 47 | |||
| 48 | // Create UX payloads and Package payloads and Container packages | ||
| 49 | groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, | ||
| 50 | new[] { ComplexReferenceChildType.ContainerPackage, ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); | ||
| 51 | groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false); | ||
| 52 | groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false); | ||
| 53 | groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false); | ||
| 54 | |||
| 55 | // Create Chain packages... | ||
| 56 | groups.UseTypes(new[] { ComplexReferenceParentType.PackageGroup }, new[] { ComplexReferenceChildType.Package, ComplexReferenceChildType.PackageGroup }); | ||
| 57 | groups.FlattenAndRewriteRows(ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, false); | ||
| 58 | |||
| 59 | groups.RemoveUsedGroupRows(); | ||
| 60 | } | ||
| 61 | |||
| 62 | private void ProcessBundleComplexReferences() | ||
| 63 | { | ||
| 64 | var containersById = this.EntrySection.Symbols.OfType<WixBundleContainerSymbol>().ToDictionary(c => c.Id.Id); | ||
| 65 | var groups = this.EntrySection.Symbols.OfType<WixGroupSymbol>().ToList(); | ||
| 66 | var payloadsById = this.EntrySection.Symbols.OfType<WixBundlePayloadSymbol>().ToDictionary(c => c.Id.Id); | ||
| 67 | |||
| 68 | var containerByPackage = new Dictionary<string, WixBundleContainerSymbol>(); | ||
| 69 | var referencedPackages = new HashSet<string>(); | ||
| 70 | var payloadsInBA = new HashSet<string>(); | ||
| 71 | var payloadsInPackageOrLayout = new HashSet<string>(); | ||
| 72 | |||
| 73 | foreach (var groupSymbol in groups) | ||
| 74 | { | ||
| 75 | switch (groupSymbol.ChildType) | ||
| 76 | { | ||
| 77 | case ComplexReferenceChildType.ContainerPackage: | ||
| 78 | switch (groupSymbol.ParentType) | ||
| 79 | { | ||
| 80 | case ComplexReferenceParentType.Container: | ||
| 81 | if (containerByPackage.TryGetValue(groupSymbol.ChildId, out var collisionContainer)) | ||
| 82 | { | ||
| 83 | this.Messaging.Write(LinkerErrors.PackageInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, groupSymbol.ParentId, collisionContainer.Id.Id)); | ||
| 84 | } | ||
| 85 | else | ||
| 86 | { | ||
| 87 | containerByPackage.Add(groupSymbol.ChildId, containersById[groupSymbol.ParentId]); | ||
| 88 | } | ||
| 89 | break; | ||
| 90 | } | ||
| 91 | break; | ||
| 92 | case ComplexReferenceChildType.Package: | ||
| 93 | switch (groupSymbol.ParentType) | ||
| 94 | { | ||
| 95 | case ComplexReferenceParentType.PackageGroup: | ||
| 96 | if (groupSymbol.ParentId == BurnConstants.BundleChainPackageGroupId) | ||
| 97 | { | ||
| 98 | referencedPackages.Add(groupSymbol.ChildId); | ||
| 99 | } | ||
| 100 | break; | ||
| 101 | } | ||
| 102 | break; | ||
| 103 | case ComplexReferenceChildType.Payload: | ||
| 104 | switch (groupSymbol.ParentType) | ||
| 105 | { | ||
| 106 | case ComplexReferenceParentType.Container: | ||
| 107 | if (groupSymbol.ParentId == BurnConstants.BurnUXContainerName) | ||
| 108 | { | ||
| 109 | payloadsInBA.Add(groupSymbol.ChildId); | ||
| 110 | } | ||
| 111 | break; | ||
| 112 | case ComplexReferenceParentType.Layout: | ||
| 113 | payloadsById[groupSymbol.ChildId].LayoutOnly = true; | ||
| 114 | payloadsInPackageOrLayout.Add(groupSymbol.ChildId); | ||
| 115 | break; | ||
| 116 | case ComplexReferenceParentType.Package: | ||
| 117 | payloadsInPackageOrLayout.Add(groupSymbol.ChildId); | ||
| 118 | break; | ||
| 119 | } | ||
| 120 | break; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | foreach (var package in this.EntrySection.Symbols.OfType<WixBundlePackageSymbol>()) | ||
| 125 | { | ||
| 126 | if (!referencedPackages.Contains(package.Id.Id)) | ||
| 127 | { | ||
| 128 | this.Messaging.Write(LinkerErrors.UnscheduledChainPackage(package.SourceLineNumbers, package.Id.Id)); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | foreach (var rollbackBoundary in this.EntrySection.Symbols.OfType<WixBundleRollbackBoundarySymbol>()) | ||
| 133 | { | ||
| 134 | if (!referencedPackages.Contains(rollbackBoundary.Id.Id)) | ||
| 135 | { | ||
| 136 | this.Messaging.Write(LinkerErrors.UnscheduledRollbackBoundary(rollbackBoundary.SourceLineNumbers, rollbackBoundary.Id.Id)); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | foreach (var payload in payloadsById.Values) | ||
| 141 | { | ||
| 142 | var payloadId = payload.Id.Id; | ||
| 143 | if (payloadsInBA.Contains(payloadId)) | ||
| 144 | { | ||
| 145 | if (payloadsInPackageOrLayout.Contains(payloadId)) | ||
| 146 | { | ||
| 147 | this.Messaging.Write(LinkerErrors.PayloadSharedWithBA(payload.SourceLineNumbers, payloadId)); | ||
| 148 | } | ||
| 149 | } | ||
| 150 | else if (!payloadsInPackageOrLayout.Contains(payloadId)) | ||
| 151 | { | ||
| 152 | this.Messaging.Write(LinkerErrors.OrphanedPayload(payload.SourceLineNumbers, payloadId)); | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | if (this.Messaging.EncounteredError) | ||
| 157 | { | ||
| 158 | return; | ||
| 159 | } | ||
| 160 | |||
| 161 | // Assign authored payloads to authored containers. | ||
| 162 | // Compressed Payloads not assigned to a container here will get assigned to the default attached container during binding. | ||
| 163 | foreach (var groupSymbol in groups) | ||
| 164 | { | ||
| 165 | if (groupSymbol.ChildType == ComplexReferenceChildType.Payload && groupSymbol.ParentType == ComplexReferenceParentType.Container) | ||
| 166 | { | ||
| 167 | var payloadSymbol = payloadsById[groupSymbol.ChildId]; | ||
| 168 | var containerId = groupSymbol.ParentId; | ||
| 169 | |||
| 170 | if (String.IsNullOrEmpty(payloadSymbol.ContainerRef)) | ||
| 171 | { | ||
| 172 | if (payloadSymbol.Compressed == false) | ||
| 173 | { | ||
| 174 | this.Messaging.Write(LinkerWarnings.UncompressedPayloadInContainer(payloadSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); | ||
| 175 | } | ||
| 176 | |||
| 177 | payloadSymbol.Compressed = true; | ||
| 178 | payloadSymbol.ContainerRef = containerId; | ||
| 179 | } | ||
| 180 | else | ||
| 181 | { | ||
| 182 | this.Messaging.Write(LinkerWarnings.PayloadInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId, payloadSymbol.ContainerRef)); | ||
| 183 | } | ||
| 184 | |||
| 185 | if (payloadSymbol.LayoutOnly) | ||
| 186 | { | ||
| 187 | this.Messaging.Write(LinkerWarnings.LayoutPayloadInContainer(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); | ||
| 188 | } | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs b/src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs new file mode 100644 index 00000000..cbf48abe --- /dev/null +++ b/src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | internal static class IntermediateSymbolExtensions | ||
| 8 | { | ||
| 9 | public static bool IsIdentical(this IntermediateSymbol first, IntermediateSymbol second) | ||
| 10 | { | ||
| 11 | var identical = (first.Definition.Type == second.Definition.Type && | ||
| 12 | (first.Definition.Type != SymbolDefinitionType.MustBeFromAnExtension || first.Definition.Name == second.Definition.Name) && | ||
| 13 | first.Definition.FieldDefinitions.Length == second.Definition.FieldDefinitions.Length); | ||
| 14 | |||
| 15 | for (var i = 0; identical && i < first.Definition.FieldDefinitions.Length; ++i) | ||
| 16 | { | ||
| 17 | var firstField = first[i]; | ||
| 18 | var secondField = second[i]; | ||
| 19 | |||
| 20 | identical = (firstField.AsString() == secondField.AsString()); | ||
| 21 | } | ||
| 22 | |||
| 23 | return identical; | ||
| 24 | } | ||
| 25 | } | ||
| 26 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs b/src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs new file mode 100644 index 00000000..ace2e19d --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.Linq; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Extensibility.Services; | ||
| 9 | |||
| 10 | internal class ReportConflictingSymbolsCommand | ||
| 11 | { | ||
| 12 | public ReportConflictingSymbolsCommand(IMessaging messaging, IEnumerable<SymbolWithSection> possibleConflicts, IEnumerable<IntermediateSection> resolvedSections) | ||
| 13 | { | ||
| 14 | this.Messaging = messaging; | ||
| 15 | this.PossibleConflicts = possibleConflicts; | ||
| 16 | this.ResolvedSections = resolvedSections; | ||
| 17 | } | ||
| 18 | |||
| 19 | private IMessaging Messaging { get; } | ||
| 20 | |||
| 21 | private IEnumerable<SymbolWithSection> PossibleConflicts { get; } | ||
| 22 | |||
| 23 | private IEnumerable<IntermediateSection> ResolvedSections { get; } | ||
| 24 | |||
| 25 | public void Execute() | ||
| 26 | { | ||
| 27 | // Do a quick check if there are any possibly conflicting symbols that don't come from tables that allow | ||
| 28 | // overriding. Hopefully the symbols with possible conflicts list is usually very short list (empty should | ||
| 29 | // be the most common). If we find any matches, we'll do a more costly check to see if the possible conflicting | ||
| 30 | // symbols are in sections we actually referenced. From the resulting set, show an error for each duplicate | ||
| 31 | // (aka: conflicting) symbol. | ||
| 32 | var illegalDuplicates = this.PossibleConflicts.Where(s => s.Symbol.Definition.Type != SymbolDefinitionType.WixAction && s.Symbol.Definition.Type != SymbolDefinitionType.WixVariable).ToList(); | ||
| 33 | if (0 < illegalDuplicates.Count) | ||
| 34 | { | ||
| 35 | var referencedSections = new HashSet<IntermediateSection>(this.ResolvedSections); | ||
| 36 | |||
| 37 | foreach (var referencedDuplicate in illegalDuplicates.Where(s => referencedSections.Contains(s.Section))) | ||
| 38 | { | ||
| 39 | var actuallyReferencedDuplicates = referencedDuplicate.PossiblyConflicts.Where(s => referencedSections.Contains(s.Section)).ToList(); | ||
| 40 | |||
| 41 | if (actuallyReferencedDuplicates.Any()) | ||
| 42 | { | ||
| 43 | this.Messaging.Write(ErrorMessages.DuplicateSymbol(referencedDuplicate.Symbol.SourceLineNumbers, referencedDuplicate.Name)); | ||
| 44 | |||
| 45 | foreach (var duplicate in actuallyReferencedDuplicates) | ||
| 46 | { | ||
| 47 | this.Messaging.Write(ErrorMessages.DuplicateSymbol2(duplicate.Symbol.SourceLineNumbers)); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs new file mode 100644 index 00000000..efb90bb8 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs | |||
| @@ -0,0 +1,183 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Symbols; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Resolves all the simple references in a section. | ||
| 14 | /// </summary> | ||
| 15 | internal class ResolveReferencesCommand | ||
| 16 | { | ||
| 17 | private readonly IntermediateSection entrySection; | ||
| 18 | private readonly IDictionary<string, SymbolWithSection> symbolsWithSections; | ||
| 19 | private HashSet<SymbolWithSection> referencedSymbols; | ||
| 20 | private HashSet<IntermediateSection> resolvedSections; | ||
| 21 | |||
| 22 | public ResolveReferencesCommand(IMessaging messaging, IntermediateSection entrySection, IDictionary<string, SymbolWithSection> symbolsWithSections) | ||
| 23 | { | ||
| 24 | this.Messaging = messaging; | ||
| 25 | this.entrySection = entrySection; | ||
| 26 | this.symbolsWithSections = symbolsWithSections; | ||
| 27 | this.BuildingMergeModule = (SectionType.Module == entrySection.Type); | ||
| 28 | } | ||
| 29 | |||
| 30 | public IEnumerable<SymbolWithSection> ReferencedSymbolWithSections => this.referencedSymbols; | ||
| 31 | |||
| 32 | public IEnumerable<IntermediateSection> ResolvedSections => this.resolvedSections; | ||
| 33 | |||
| 34 | private bool BuildingMergeModule { get; } | ||
| 35 | |||
| 36 | private IMessaging Messaging { get; } | ||
| 37 | |||
| 38 | /// <summary> | ||
| 39 | /// Resolves all the simple references in a section. | ||
| 40 | /// </summary> | ||
| 41 | public void Execute() | ||
| 42 | { | ||
| 43 | this.resolvedSections = new HashSet<IntermediateSection>(); | ||
| 44 | this.referencedSymbols = new HashSet<SymbolWithSection>(); | ||
| 45 | |||
| 46 | this.RecursivelyResolveReferences(this.entrySection); | ||
| 47 | } | ||
| 48 | |||
| 49 | /// <summary> | ||
| 50 | /// Recursive helper function to resolve all references of passed in section. | ||
| 51 | /// </summary> | ||
| 52 | /// <param name="section">Section with references to resolve.</param> | ||
| 53 | /// <remarks>Note: recursive function.</remarks> | ||
| 54 | private void RecursivelyResolveReferences(IntermediateSection section) | ||
| 55 | { | ||
| 56 | // If we already resolved this section, move on to the next. | ||
| 57 | if (!this.resolvedSections.Add(section)) | ||
| 58 | { | ||
| 59 | return; | ||
| 60 | } | ||
| 61 | |||
| 62 | // Process all of the references contained in this section using the collection of | ||
| 63 | // symbols provided. Then recursively call this method to process the | ||
| 64 | // located symbol's section. All in all this is a very simple depth-first | ||
| 65 | // search of the references per-section. | ||
| 66 | foreach (var wixSimpleReferenceRow in section.Symbols.OfType<WixSimpleReferenceSymbol>()) | ||
| 67 | { | ||
| 68 | // If we're building a Merge Module, ignore all references to the Media table | ||
| 69 | // because Merge Modules don't have Media tables. | ||
| 70 | if (this.BuildingMergeModule && wixSimpleReferenceRow.Table == "Media") | ||
| 71 | { | ||
| 72 | continue; | ||
| 73 | } | ||
| 74 | |||
| 75 | // See if the symbol (and any of its duplicates) are appropriately accessible. | ||
| 76 | if (this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection)) | ||
| 77 | { | ||
| 78 | var accessible = this.DetermineAccessibleSymbols(section, symbolWithSection); | ||
| 79 | if (accessible.Count == 1) | ||
| 80 | { | ||
| 81 | var accessibleSymbol = accessible[0]; | ||
| 82 | if (this.referencedSymbols.Add(accessibleSymbol) && null != accessibleSymbol.Section) | ||
| 83 | { | ||
| 84 | this.RecursivelyResolveReferences(accessibleSymbol.Section); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | else if (accessible.Count == 0) | ||
| 88 | { | ||
| 89 | this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access)); | ||
| 90 | } | ||
| 91 | else // display errors for the duplicate symbols. | ||
| 92 | { | ||
| 93 | var accessibleSymbol = accessible[0]; | ||
| 94 | var referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers?.ToString(); | ||
| 95 | |||
| 96 | if (String.IsNullOrEmpty(referencingSourceLineNumber)) | ||
| 97 | { | ||
| 98 | this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name)); | ||
| 99 | } | ||
| 100 | else | ||
| 101 | { | ||
| 102 | this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber)); | ||
| 103 | } | ||
| 104 | |||
| 105 | foreach (var accessibleDuplicate in accessible.Skip(1)) | ||
| 106 | { | ||
| 107 | this.Messaging.Write(ErrorMessages.DuplicateSymbol2(accessibleDuplicate.Symbol.SourceLineNumbers)); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | else | ||
| 112 | { | ||
| 113 | this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName)); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | /// <summary> | ||
| 119 | /// Determine if the symbol and any of its duplicates are accessbile by referencing section. | ||
| 120 | /// </summary> | ||
| 121 | /// <param name="referencingSection">Section referencing the symbol.</param> | ||
| 122 | /// <param name="symbolWithSection">Symbol being referenced.</param> | ||
| 123 | /// <returns>List of symbols accessible by referencing section.</returns> | ||
| 124 | private List<SymbolWithSection> DetermineAccessibleSymbols(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) | ||
| 125 | { | ||
| 126 | var accessibleSymbols = new List<SymbolWithSection>(); | ||
| 127 | |||
| 128 | if (this.AccessibleSymbol(referencingSection, symbolWithSection)) | ||
| 129 | { | ||
| 130 | accessibleSymbols.Add(symbolWithSection); | ||
| 131 | } | ||
| 132 | |||
| 133 | foreach (var dupe in symbolWithSection.PossiblyConflicts) | ||
| 134 | { | ||
| 135 | // don't count overridable WixActionSymbols | ||
| 136 | var symbolAction = symbolWithSection.Symbol as WixActionSymbol; | ||
| 137 | var dupeAction = dupe.Symbol as WixActionSymbol; | ||
| 138 | if (symbolAction?.Overridable != dupeAction?.Overridable) | ||
| 139 | { | ||
| 140 | continue; | ||
| 141 | } | ||
| 142 | |||
| 143 | if (this.AccessibleSymbol(referencingSection, dupe)) | ||
| 144 | { | ||
| 145 | accessibleSymbols.Add(dupe); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | foreach (var dupe in symbolWithSection.Redundants) | ||
| 150 | { | ||
| 151 | if (this.AccessibleSymbol(referencingSection, dupe)) | ||
| 152 | { | ||
| 153 | accessibleSymbols.Add(dupe); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | return accessibleSymbols; | ||
| 158 | } | ||
| 159 | |||
| 160 | /// <summary> | ||
| 161 | /// Determine if a single symbol is accessible by the referencing section. | ||
| 162 | /// </summary> | ||
| 163 | /// <param name="referencingSection">Section referencing the symbol.</param> | ||
| 164 | /// <param name="symbolWithSection">Symbol being referenced.</param> | ||
| 165 | /// <returns>True if symbol is accessible.</returns> | ||
| 166 | private bool AccessibleSymbol(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) | ||
| 167 | { | ||
| 168 | switch (symbolWithSection.Access) | ||
| 169 | { | ||
| 170 | case AccessModifier.Global: | ||
| 171 | return true; | ||
| 172 | case AccessModifier.Library: | ||
| 173 | return symbolWithSection.Section.CompilationId == referencingSection.CompilationId || (null != symbolWithSection.Section.LibraryId && symbolWithSection.Section.LibraryId == referencingSection.LibraryId); | ||
| 174 | case AccessModifier.File: | ||
| 175 | return symbolWithSection.Section.CompilationId == referencingSection.CompilationId; | ||
| 176 | case AccessModifier.Section: | ||
| 177 | return referencingSection == symbolWithSection.Section; | ||
| 178 | default: | ||
| 179 | throw new ArgumentOutOfRangeException(nameof(symbolWithSection.Access)); | ||
| 180 | } | ||
| 181 | } | ||
| 182 | } | ||
| 183 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/SymbolWithSection.cs b/src/wix/WixToolset.Core/Link/SymbolWithSection.cs new file mode 100644 index 00000000..08e01077 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/SymbolWithSection.cs | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Symbol with section representing a single unique symbol. | ||
| 12 | /// </summary> | ||
| 13 | internal class SymbolWithSection | ||
| 14 | { | ||
| 15 | private HashSet<SymbolWithSection> possibleConflicts; | ||
| 16 | private HashSet<SymbolWithSection> redundants; | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// Creates a symbol for a symbol. | ||
| 20 | /// </summary> | ||
| 21 | /// <param name="section"></param> | ||
| 22 | /// <param name="symbol">Symbol for the symbol</param> | ||
| 23 | public SymbolWithSection(IntermediateSection section, IntermediateSymbol symbol) | ||
| 24 | { | ||
| 25 | this.Symbol = symbol; | ||
| 26 | this.Section = section; | ||
| 27 | this.Name = String.Concat(this.Symbol.Definition.Name, ":", this.Symbol.Id.Id); | ||
| 28 | } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Gets the accessibility of the symbol which is a direct reflection of the accessibility of the row's accessibility. | ||
| 32 | /// </summary> | ||
| 33 | /// <value>Accessbility of the symbol.</value> | ||
| 34 | public AccessModifier Access => this.Symbol.Id.Access; | ||
| 35 | |||
| 36 | /// <summary> | ||
| 37 | /// Gets the name of the symbol. | ||
| 38 | /// </summary> | ||
| 39 | /// <value>Name of the symbol.</value> | ||
| 40 | public string Name { get; } | ||
| 41 | |||
| 42 | /// <summary> | ||
| 43 | /// Gets the symbol for this symbol. | ||
| 44 | /// </summary> | ||
| 45 | /// <value>Symbol for this symbol.</value> | ||
| 46 | public IntermediateSymbol Symbol { get; } | ||
| 47 | |||
| 48 | /// <summary> | ||
| 49 | /// Gets the section for the symbol. | ||
| 50 | /// </summary> | ||
| 51 | /// <value>Section for the symbol.</value> | ||
| 52 | public IntermediateSection Section { get; } | ||
| 53 | |||
| 54 | /// <summary> | ||
| 55 | /// Gets any duplicates of this symbol with sections that are possible conflicts. | ||
| 56 | /// </summary> | ||
| 57 | public IEnumerable<SymbolWithSection> PossiblyConflicts => this.possibleConflicts ?? Enumerable.Empty<SymbolWithSection>(); | ||
| 58 | |||
| 59 | /// <summary> | ||
| 60 | /// Gets any duplicates of this symbol with sections that are redundant. | ||
| 61 | /// </summary> | ||
| 62 | public IEnumerable<SymbolWithSection> Redundants => this.redundants ?? Enumerable.Empty<SymbolWithSection>(); | ||
| 63 | |||
| 64 | /// <summary> | ||
| 65 | /// Adds a duplicate symbol with sections that is a possible conflict. | ||
| 66 | /// </summary> | ||
| 67 | /// <param name="symbolWithSection">Symbol with section that is a possible conflict of this symbol.</param> | ||
| 68 | public void AddPossibleConflict(SymbolWithSection symbolWithSection) | ||
| 69 | { | ||
| 70 | if (null == this.possibleConflicts) | ||
| 71 | { | ||
| 72 | this.possibleConflicts = new HashSet<SymbolWithSection>(); | ||
| 73 | } | ||
| 74 | |||
| 75 | this.possibleConflicts.Add(symbolWithSection); | ||
| 76 | } | ||
| 77 | |||
| 78 | /// <summary> | ||
| 79 | /// Adds a duplicate symbol that is redundant. | ||
| 80 | /// </summary> | ||
| 81 | /// <param name="symbolWithSection">Symbol with section that is redundant of this symbol.</param> | ||
| 82 | public void AddRedundant(SymbolWithSection symbolWithSection) | ||
| 83 | { | ||
| 84 | if (null == this.redundants) | ||
| 85 | { | ||
| 86 | this.redundants = new HashSet<SymbolWithSection>(); | ||
| 87 | } | ||
| 88 | |||
| 89 | this.redundants.Add(symbolWithSection); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs b/src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs new file mode 100644 index 00000000..2b1925ad --- /dev/null +++ b/src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Data.Symbols; | ||
| 7 | |||
| 8 | internal static class WixComplexReferenceSymbolExtensions | ||
| 9 | { | ||
| 10 | /// <summary> | ||
| 11 | /// Creates a shallow copy of the ComplexReference. | ||
| 12 | /// </summary> | ||
| 13 | /// <returns>A shallow copy of the ComplexReference.</returns> | ||
| 14 | public static WixComplexReferenceSymbol Clone(this WixComplexReferenceSymbol source) | ||
| 15 | { | ||
| 16 | var clone = new WixComplexReferenceSymbol(source.SourceLineNumbers, source.Id); | ||
| 17 | clone.ParentType = source.ParentType; | ||
| 18 | clone.Parent = source.Parent; | ||
| 19 | clone.ParentLanguage = source.ParentLanguage; | ||
| 20 | clone.ChildType = source.ChildType; | ||
| 21 | clone.Child = source.Child; | ||
| 22 | clone.IsPrimary = source.IsPrimary; | ||
| 23 | |||
| 24 | return clone; | ||
| 25 | } | ||
| 26 | |||
| 27 | /// <summary> | ||
| 28 | /// Compares two complex references without considering the primary bit. | ||
| 29 | /// </summary> | ||
| 30 | /// <param name="symbol">this</param> | ||
| 31 | /// <param name="other">Complex reference to compare to.</param> | ||
| 32 | /// <returns>Zero if the objects are equivalent, negative number if the provided object is less, positive if greater.</returns> | ||
| 33 | public static int CompareToWithoutConsideringPrimary(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol other) | ||
| 34 | { | ||
| 35 | var comparison = symbol.ChildType - other.ChildType; | ||
| 36 | if (0 == comparison) | ||
| 37 | { | ||
| 38 | comparison = String.Compare(symbol.Child, other.Child, StringComparison.Ordinal); | ||
| 39 | if (0 == comparison) | ||
| 40 | { | ||
| 41 | comparison = symbol.ParentType - other.ParentType; | ||
| 42 | if (0 == comparison) | ||
| 43 | { | ||
| 44 | string thisParentLanguage = null == symbol.ParentLanguage ? String.Empty : symbol.ParentLanguage; | ||
| 45 | string otherParentLanguage = null == other.ParentLanguage ? String.Empty : other.ParentLanguage; | ||
| 46 | comparison = String.Compare(thisParentLanguage, otherParentLanguage, StringComparison.Ordinal); | ||
| 47 | if (0 == comparison) | ||
| 48 | { | ||
| 49 | comparison = String.Compare(symbol.Parent, other.Parent, StringComparison.Ordinal); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | return comparison; | ||
| 56 | } | ||
| 57 | |||
| 58 | /// <summary> | ||
| 59 | /// Changes all of the parent references to point to the passed in parent reference. | ||
| 60 | /// </summary> | ||
| 61 | /// <param name="symbol">this</param> | ||
| 62 | /// <param name="parent">New parent complex reference.</param> | ||
| 63 | public static void Reparent(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol parent) | ||
| 64 | { | ||
| 65 | symbol.Parent = parent.Parent; | ||
| 66 | symbol.ParentLanguage = parent.ParentLanguage; | ||
| 67 | symbol.ParentType = parent.ParentType; | ||
| 68 | |||
| 69 | if (!symbol.IsPrimary) | ||
| 70 | { | ||
| 71 | symbol.IsPrimary = parent.IsPrimary; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
diff --git a/src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs new file mode 100644 index 00000000..f9de82a9 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs | |||
| @@ -0,0 +1,683 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.ObjectModel; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Diagnostics; | ||
| 9 | using System.Globalization; | ||
| 10 | using System.Linq; | ||
| 11 | using System.Text; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Extensibility.Services; | ||
| 15 | using WixToolset.Data.Burn; | ||
| 16 | |||
| 17 | /// <summary> | ||
| 18 | /// Grouping and Ordering class of the WiX toolset. | ||
| 19 | /// </summary> | ||
| 20 | internal class WixGroupingOrdering | ||
| 21 | { | ||
| 22 | private readonly IMessaging Messaging; | ||
| 23 | private List<string> groupTypes; | ||
| 24 | private List<string> itemTypes; | ||
| 25 | private ItemCollection items; | ||
| 26 | private readonly List<IntermediateSymbol> symbolsUsed; | ||
| 27 | private bool loaded; | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// Creates a WixGroupingOrdering object. | ||
| 31 | /// </summary> | ||
| 32 | /// <param name="entrySections">Output from which to read the group and order information.</param> | ||
| 33 | /// <param name="messageHandler">Handler for any error messages.</param> | ||
| 34 | public WixGroupingOrdering(IntermediateSection entrySections, IMessaging messageHandler) | ||
| 35 | { | ||
| 36 | this.EntrySection = entrySections; | ||
| 37 | this.Messaging = messageHandler; | ||
| 38 | |||
| 39 | this.symbolsUsed = new List<IntermediateSymbol>(); | ||
| 40 | this.loaded = false; | ||
| 41 | } | ||
| 42 | |||
| 43 | private IntermediateSection EntrySection { get; } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Switches a WixGroupingOrdering object to operate on a new set of groups/items. | ||
| 47 | /// </summary> | ||
| 48 | /// <param name="groupTypes">Group types to include.</param> | ||
| 49 | /// <param name="itemTypes">Item types to include.</param> | ||
| 50 | public void UseTypes(IEnumerable<ComplexReferenceParentType> groupTypes, IEnumerable<ComplexReferenceChildType> itemTypes) | ||
| 51 | { | ||
| 52 | this.groupTypes = new List<string>(groupTypes.Select(g => g.ToString())); | ||
| 53 | this.itemTypes = new List<string>(itemTypes.Select(i => i.ToString())); | ||
| 54 | |||
| 55 | this.items = new ItemCollection(); | ||
| 56 | this.loaded = false; | ||
| 57 | } | ||
| 58 | |||
| 59 | /// <summary> | ||
| 60 | /// Finds all nested items under a parent group and creates new WixGroup data for them. | ||
| 61 | /// </summary> | ||
| 62 | /// <param name="parentType">The group type for the parent group to flatten.</param> | ||
| 63 | /// <param name="parentId">The identifier of the parent group to flatten.</param> | ||
| 64 | /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param> | ||
| 65 | public void FlattenAndRewriteRows(ComplexReferenceParentType parentType, string parentId, bool removeUsedRows) | ||
| 66 | { | ||
| 67 | var parentTypeString = parentType.ToString(); | ||
| 68 | Debug.Assert(this.groupTypes.Contains(parentTypeString)); | ||
| 69 | |||
| 70 | this.CreateOrderedList(parentTypeString, parentId, out var orderedItems); | ||
| 71 | if (this.Messaging.EncounteredError) | ||
| 72 | { | ||
| 73 | return; | ||
| 74 | } | ||
| 75 | |||
| 76 | this.CreateNewGroupRows(parentTypeString, parentId, orderedItems); | ||
| 77 | |||
| 78 | if (removeUsedRows) | ||
| 79 | { | ||
| 80 | this.RemoveUsedGroupRows(); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | /// <summary> | ||
| 85 | /// Finds all items under a parent group type and creates new WixGroup data for them. | ||
| 86 | /// </summary> | ||
| 87 | /// <param name="parentType">The type of the parent group to flatten.</param> | ||
| 88 | /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param> | ||
| 89 | public void FlattenAndRewriteGroups(ComplexReferenceParentType parentType, bool removeUsedRows) | ||
| 90 | { | ||
| 91 | var parentTypeString = parentType.ToString(); | ||
| 92 | Debug.Assert(this.groupTypes.Contains(parentTypeString)); | ||
| 93 | |||
| 94 | this.LoadFlattenOrderGroups(); | ||
| 95 | if (this.Messaging.EncounteredError) | ||
| 96 | { | ||
| 97 | return; | ||
| 98 | } | ||
| 99 | |||
| 100 | foreach (Item item in this.items) | ||
| 101 | { | ||
| 102 | if (parentTypeString == item.Type) | ||
| 103 | { | ||
| 104 | this.CreateOrderedList(item.Type, item.Id, out var orderedItems); | ||
| 105 | this.CreateNewGroupRows(item.Type, item.Id, orderedItems); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | if (removeUsedRows) | ||
| 110 | { | ||
| 111 | this.RemoveUsedGroupRows(); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | |||
| 116 | /// <summary> | ||
| 117 | /// Creates a flattened and ordered list of items for the given parent group. | ||
| 118 | /// </summary> | ||
| 119 | /// <param name="parentType">The group type for the parent group to flatten.</param> | ||
| 120 | /// <param name="parentId">The identifier of the parent group to flatten.</param> | ||
| 121 | /// <param name="orderedItems">The returned list of ordered items.</param> | ||
| 122 | private void CreateOrderedList(string parentType, string parentId, out List<Item> orderedItems) | ||
| 123 | { | ||
| 124 | orderedItems = null; | ||
| 125 | |||
| 126 | this.LoadFlattenOrderGroups(); | ||
| 127 | if (this.Messaging.EncounteredError) | ||
| 128 | { | ||
| 129 | return; | ||
| 130 | } | ||
| 131 | |||
| 132 | if (!this.items.TryGetValue(parentType, parentId, out var parentItem)) | ||
| 133 | { | ||
| 134 | this.Messaging.Write(ErrorMessages.IdentifierNotFound(parentType, parentId)); | ||
| 135 | return; | ||
| 136 | } | ||
| 137 | |||
| 138 | orderedItems = new List<Item>(parentItem.ChildItems); | ||
| 139 | orderedItems.Sort(new Item.AfterItemComparer()); | ||
| 140 | } | ||
| 141 | |||
| 142 | /// <summary> | ||
| 143 | /// Removes rows from WixGroup that have been used by this object. | ||
| 144 | /// </summary> | ||
| 145 | public void RemoveUsedGroupRows() | ||
| 146 | { | ||
| 147 | foreach (var symbol in this.symbolsUsed) | ||
| 148 | { | ||
| 149 | this.EntrySection.RemoveSymbol(symbol); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | /// <summary> | ||
| 154 | /// Creates new WixGroup rows for a list of items. | ||
| 155 | /// </summary> | ||
| 156 | /// <param name="parentType">The group type for the parent group in the new rows.</param> | ||
| 157 | /// <param name="parentId">The identifier of the parent group in the new rows.</param> | ||
| 158 | /// <param name="orderedItems">The list of new items.</param> | ||
| 159 | private void CreateNewGroupRows(string parentType, string parentId, List<Item> orderedItems) | ||
| 160 | { | ||
| 161 | // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither | ||
| 162 | // does WiX (although they do, currently). We probably want to "upgrade" this to a new | ||
| 163 | // table that includes a sequence number, and then change the code that uses ordered | ||
| 164 | // groups to read from that table instead. | ||
| 165 | foreach (var item in orderedItems) | ||
| 166 | { | ||
| 167 | this.EntrySection.AddSymbol(new WixGroupSymbol(item.Row.SourceLineNumbers) | ||
| 168 | { | ||
| 169 | ParentId = parentId, | ||
| 170 | ParentType = (ComplexReferenceParentType)Enum.Parse(typeof(ComplexReferenceParentType), parentType), | ||
| 171 | ChildId = item.Id, | ||
| 172 | ChildType = (ComplexReferenceChildType)Enum.Parse(typeof(ComplexReferenceChildType), item.Type), | ||
| 173 | }); | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | // Group/Ordering Flattening Logic | ||
| 178 | // | ||
| 179 | // What follows is potentially convoluted logic. Two somewhat orthogonal concepts are in | ||
| 180 | // play: grouping (parent/child relationships) and ordering (before/after relationships). | ||
| 181 | // Dealing with just one or the other is straghtforward. Groups can be flattened | ||
| 182 | // recursively. Ordering can be propagated in either direction. When the ordering also | ||
| 183 | // participates in the grouping constructions, however, things get trickier. For the | ||
| 184 | // purposes of this discussion, we're dealing with "items" and "groups", and an instance | ||
| 185 | // of either of them can be marked as coming "after" some other instance. | ||
| 186 | // | ||
| 187 | // For simple item-to-item ordering, the "after" values simply propagate: if A is after B, | ||
| 188 | // and B is after C, then we can say that A is after *both* B and C. If a group is involved, | ||
| 189 | // it acts as a proxy for all of its included items and any sub-groups. | ||
| 190 | |||
| 191 | /// <summary> | ||
| 192 | /// Internal workhorse for ensuring that group and ordering information has | ||
| 193 | /// been loaded and applied. | ||
| 194 | /// </summary> | ||
| 195 | private void LoadFlattenOrderGroups() | ||
| 196 | { | ||
| 197 | if (!this.loaded) | ||
| 198 | { | ||
| 199 | this.LoadGroups(); | ||
| 200 | this.LoadOrdering(); | ||
| 201 | |||
| 202 | // It would be really nice to have a "find circular after dependencies" | ||
| 203 | // function, but it gets much more complicated because of the way that | ||
| 204 | // the dependencies are propagated across group boundaries. For now, we | ||
| 205 | // just live with the dependency loop detection as we flatten the | ||
| 206 | // dependencies. Group references, however, we can check directly. | ||
| 207 | this.FindCircularGroupReferences(); | ||
| 208 | |||
| 209 | if (!this.Messaging.EncounteredError) | ||
| 210 | { | ||
| 211 | this.FlattenGroups(); | ||
| 212 | this.FlattenOrdering(); | ||
| 213 | } | ||
| 214 | |||
| 215 | this.loaded = true; | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | /// <summary> | ||
| 220 | /// Loads data from the WixGroup table. | ||
| 221 | /// </summary> | ||
| 222 | private void LoadGroups() | ||
| 223 | { | ||
| 224 | //Table wixGroupTable = this.output.Tables["WixGroup"]; | ||
| 225 | //if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count) | ||
| 226 | //{ | ||
| 227 | // // TODO: Change message name to make it *not* Bundle specific? | ||
| 228 | // this.Write(WixErrors.MissingBundleInformation("WixGroup")); | ||
| 229 | //} | ||
| 230 | |||
| 231 | // Collect all of the groups | ||
| 232 | foreach (var symbol in this.EntrySection.Symbols.OfType<WixGroupSymbol>()) | ||
| 233 | { | ||
| 234 | var rowParentName = symbol.ParentId; | ||
| 235 | var rowParentType = symbol.ParentType.ToString(); | ||
| 236 | var rowChildName = symbol.ChildId; | ||
| 237 | var rowChildType = symbol.ChildType.ToString(); | ||
| 238 | |||
| 239 | // If this row specifies a parent or child type that's not in our | ||
| 240 | // lists, we assume it's not a row that we're concerned about. | ||
| 241 | if (!this.groupTypes.Contains(rowParentType) || | ||
| 242 | !this.itemTypes.Contains(rowChildType)) | ||
| 243 | { | ||
| 244 | continue; | ||
| 245 | } | ||
| 246 | |||
| 247 | this.symbolsUsed.Add(symbol); | ||
| 248 | |||
| 249 | if (!this.items.TryGetValue(rowParentType, rowParentName, out var parentItem)) | ||
| 250 | { | ||
| 251 | parentItem = new Item(symbol, rowParentType, rowParentName); | ||
| 252 | this.items.Add(parentItem); | ||
| 253 | } | ||
| 254 | |||
| 255 | if (!this.items.TryGetValue(rowChildType, rowChildName, out var childItem)) | ||
| 256 | { | ||
| 257 | childItem = new Item(symbol, rowChildType, rowChildName); | ||
| 258 | this.items.Add(childItem); | ||
| 259 | } | ||
| 260 | |||
| 261 | parentItem.ChildItems.Add(childItem); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | /// <summary> | ||
| 266 | /// Flattens group/item information. | ||
| 267 | /// </summary> | ||
| 268 | private void FlattenGroups() | ||
| 269 | { | ||
| 270 | foreach (Item item in this.items) | ||
| 271 | { | ||
| 272 | item.FlattenChildItems(); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | /// <summary> | ||
| 277 | /// Finds and reports circular references in the group/item data. | ||
| 278 | /// </summary> | ||
| 279 | private void FindCircularGroupReferences() | ||
| 280 | { | ||
| 281 | ItemCollection itemsInKnownLoops = new ItemCollection(); | ||
| 282 | foreach (Item item in this.items) | ||
| 283 | { | ||
| 284 | if (itemsInKnownLoops.Contains(item)) | ||
| 285 | { | ||
| 286 | continue; | ||
| 287 | } | ||
| 288 | |||
| 289 | ItemCollection itemsSeen = new ItemCollection(); | ||
| 290 | string circularReference; | ||
| 291 | if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference)) | ||
| 292 | { | ||
| 293 | itemsInKnownLoops.Add(itemsSeen); | ||
| 294 | this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | /// <summary> | ||
| 300 | /// Recursive worker to find and report circular references in group/item data. | ||
| 301 | /// </summary> | ||
| 302 | /// <param name="checkItem">The sentinal item being checked.</param> | ||
| 303 | /// <param name="currentItem">The current item in the recursion.</param> | ||
| 304 | /// <param name="itemsSeen">A list of all items already visited (for performance).</param> | ||
| 305 | /// <param name="circularReference">A list of items in the current circular reference, if one was found; null otherwise.</param> | ||
| 306 | /// <returns>True if a circular reference was found; false otherwise.</returns> | ||
| 307 | private bool FindCircularGroupReference(Item checkItem, Item currentItem, ItemCollection itemsSeen, out string circularReference) | ||
| 308 | { | ||
| 309 | circularReference = null; | ||
| 310 | foreach (Item subitem in currentItem.ChildItems) | ||
| 311 | { | ||
| 312 | if (checkItem == subitem) | ||
| 313 | { | ||
| 314 | // TODO: Even better would be to include the source lines for each reference! | ||
| 315 | circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3}", | ||
| 316 | currentItem.Type, currentItem.Id, subitem.Type, subitem.Id); | ||
| 317 | return true; | ||
| 318 | } | ||
| 319 | |||
| 320 | if (!itemsSeen.Contains(subitem)) | ||
| 321 | { | ||
| 322 | itemsSeen.Add(subitem); | ||
| 323 | if (this.FindCircularGroupReference(checkItem, subitem, itemsSeen, out circularReference)) | ||
| 324 | { | ||
| 325 | // TODO: Even better would be to include the source lines for each reference! | ||
| 326 | circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}", | ||
| 327 | currentItem.Type, currentItem.Id, circularReference); | ||
| 328 | return true; | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | return false; | ||
| 334 | } | ||
| 335 | |||
| 336 | /// <summary> | ||
| 337 | /// Loads ordering dependency data from the WixOrdering table. | ||
| 338 | /// </summary> | ||
| 339 | private void LoadOrdering() | ||
| 340 | { | ||
| 341 | //Table wixOrderingTable = output.Tables["WixOrdering"]; | ||
| 342 | //if (null == wixOrderingTable || 0 == wixOrderingTable.Rows.Count) | ||
| 343 | //{ | ||
| 344 | // // TODO: Do we need a message here? | ||
| 345 | // return; | ||
| 346 | //} | ||
| 347 | |||
| 348 | foreach (var row in this.EntrySection.Symbols.OfType<WixOrderingSymbol>()) | ||
| 349 | { | ||
| 350 | var rowItemType = row.ItemType.ToString(); | ||
| 351 | var rowItemName = row.ItemIdRef; | ||
| 352 | var rowDependsOnType = row.DependsOnType.ToString(); | ||
| 353 | var rowDependsOnName = row.DependsOnIdRef; | ||
| 354 | |||
| 355 | // If this row specifies some other (unknown) type in either | ||
| 356 | // position, we assume it's not a row that we're concerned about. | ||
| 357 | // For ordering, we allow group and item in either position. | ||
| 358 | if (!(this.groupTypes.Contains(rowItemType) || this.itemTypes.Contains(rowItemType)) || | ||
| 359 | !(this.groupTypes.Contains(rowDependsOnType) || this.itemTypes.Contains(rowDependsOnType))) | ||
| 360 | { | ||
| 361 | continue; | ||
| 362 | } | ||
| 363 | |||
| 364 | if (!this.items.TryGetValue(rowItemType, rowItemName, out var item)) | ||
| 365 | { | ||
| 366 | this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName)); | ||
| 367 | } | ||
| 368 | |||
| 369 | if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out var dependsOn)) | ||
| 370 | { | ||
| 371 | this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); | ||
| 372 | } | ||
| 373 | |||
| 374 | if (null == item || null == dependsOn) | ||
| 375 | { | ||
| 376 | continue; | ||
| 377 | } | ||
| 378 | |||
| 379 | item.AddAfter(dependsOn, this.Messaging); | ||
| 380 | } | ||
| 381 | } | ||
| 382 | |||
| 383 | /// <summary> | ||
| 384 | /// Flattens the ordering dependencies in the groups/items. | ||
| 385 | /// </summary> | ||
| 386 | private void FlattenOrdering() | ||
| 387 | { | ||
| 388 | // Because items don't know about their parent groups (and can, in fact, be | ||
| 389 | // in more than one group at a time), we need to pre-propagate the 'afters' | ||
| 390 | // from each parent item to its children before we attempt to flatten the | ||
| 391 | // ordering. | ||
| 392 | foreach (Item item in this.items) | ||
| 393 | { | ||
| 394 | item.PropagateAfterToChildItems(this.Messaging); | ||
| 395 | } | ||
| 396 | |||
| 397 | foreach (Item item in this.items) | ||
| 398 | { | ||
| 399 | item.FlattenAfters(this.Messaging); | ||
| 400 | } | ||
| 401 | } | ||
| 402 | |||
| 403 | /// <summary> | ||
| 404 | /// A variant of KeyedCollection that doesn't throw when an item is re-added. | ||
| 405 | /// </summary> | ||
| 406 | /// <typeparam name="TKey">Key type for the collection.</typeparam> | ||
| 407 | /// <typeparam name="TItem">Item type for the colelction.</typeparam> | ||
| 408 | internal abstract class EnhancedKeyCollection<TKey, TItem> : KeyedCollection<TKey, TItem> | ||
| 409 | { | ||
| 410 | new public void Add(TItem item) | ||
| 411 | { | ||
| 412 | if (!this.Contains(item)) | ||
| 413 | { | ||
| 414 | base.Add(item); | ||
| 415 | } | ||
| 416 | } | ||
| 417 | |||
| 418 | public void Add(Collection<TItem> list) | ||
| 419 | { | ||
| 420 | foreach (TItem item in list) | ||
| 421 | { | ||
| 422 | this.Add(item); | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | public void Remove(Collection<TItem> list) | ||
| 427 | { | ||
| 428 | foreach (TItem item in list) | ||
| 429 | { | ||
| 430 | this.Remove(item); | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 434 | public bool TryGetValue(TKey key, out TItem item) | ||
| 435 | { | ||
| 436 | // KeyedCollection doesn't implement the TryGetValue() method, but it's | ||
| 437 | // a useful concept. We can't just always pass this to the enclosed | ||
| 438 | // Dictionary, however, because it doesn't always exist! If it does, we | ||
| 439 | // can delegate to it as one would expect. If it doesn't, we have to | ||
| 440 | // implement everything ourselves in terms of Contains(). | ||
| 441 | |||
| 442 | if (null != this.Dictionary) | ||
| 443 | { | ||
| 444 | return this.Dictionary.TryGetValue(key, out item); | ||
| 445 | } | ||
| 446 | |||
| 447 | if (this.Contains(key)) | ||
| 448 | { | ||
| 449 | item = this[key]; | ||
| 450 | return true; | ||
| 451 | } | ||
| 452 | |||
| 453 | item = default(TItem); | ||
| 454 | return false; | ||
| 455 | } | ||
| 456 | |||
| 457 | #if DEBUG | ||
| 458 | // This just makes debugging easier... | ||
| 459 | public override string ToString() | ||
| 460 | { | ||
| 461 | StringBuilder sb = new StringBuilder(); | ||
| 462 | foreach (TItem item in this) | ||
| 463 | { | ||
| 464 | sb.AppendFormat("{0}, ", item); | ||
| 465 | } | ||
| 466 | sb.Length -= 2; | ||
| 467 | return sb.ToString(); | ||
| 468 | } | ||
| 469 | #endif // DEBUG | ||
| 470 | } | ||
| 471 | |||
| 472 | /// <summary> | ||
| 473 | /// A specialized EnhancedKeyCollection, typed to Items. | ||
| 474 | /// </summary> | ||
| 475 | internal class ItemCollection : EnhancedKeyCollection<string, Item> | ||
| 476 | { | ||
| 477 | protected override string GetKeyForItem(Item item) | ||
| 478 | { | ||
| 479 | return item.Key; | ||
| 480 | } | ||
| 481 | |||
| 482 | public bool TryGetValue(string type, string id, out Item item) | ||
| 483 | { | ||
| 484 | return this.TryGetValue(CreateKeyFromTypeId(type, id), out item); | ||
| 485 | } | ||
| 486 | |||
| 487 | public static string CreateKeyFromTypeId(string type, string id) | ||
| 488 | { | ||
| 489 | return String.Format(CultureInfo.InvariantCulture, "{0}_{1}", type, id); | ||
| 490 | } | ||
| 491 | } | ||
| 492 | |||
| 493 | /// <summary> | ||
| 494 | /// An item (or group) in the grouping/ordering engine. | ||
| 495 | /// </summary> | ||
| 496 | /// <remarks>Encapsulates nested group membership and also before/after | ||
| 497 | /// ordering dependencies.</remarks> | ||
| 498 | internal class Item | ||
| 499 | { | ||
| 500 | private readonly ItemCollection afterItems; | ||
| 501 | private readonly ItemCollection beforeItems; // for checking for circular references | ||
| 502 | private bool flattenedAfterItems; | ||
| 503 | |||
| 504 | public Item(IntermediateSymbol row, string type, string id) | ||
| 505 | { | ||
| 506 | this.Row = row; | ||
| 507 | this.Type = type; | ||
| 508 | this.Id = id; | ||
| 509 | |||
| 510 | this.Key = ItemCollection.CreateKeyFromTypeId(type, id); | ||
| 511 | |||
| 512 | this.afterItems = new ItemCollection(); | ||
| 513 | this.beforeItems = new ItemCollection(); | ||
| 514 | this.flattenedAfterItems = false; | ||
| 515 | } | ||
| 516 | |||
| 517 | public IntermediateSymbol Row { get; private set; } | ||
| 518 | public string Type { get; private set; } | ||
| 519 | public string Id { get; private set; } | ||
| 520 | public string Key { get; private set; } | ||
| 521 | |||
| 522 | #if DEBUG | ||
| 523 | // Makes debugging easier... | ||
| 524 | public override string ToString() | ||
| 525 | { | ||
| 526 | return this.Key; | ||
| 527 | } | ||
| 528 | #endif // DEBUG | ||
| 529 | |||
| 530 | public ItemCollection ChildItems { get; } = new ItemCollection(); | ||
| 531 | |||
| 532 | /// <summary> | ||
| 533 | /// Removes any nested groups under this item and replaces | ||
| 534 | /// them with their child items. | ||
| 535 | /// </summary> | ||
| 536 | public void FlattenChildItems() | ||
| 537 | { | ||
| 538 | ItemCollection flattenedChildItems = new ItemCollection(); | ||
| 539 | |||
| 540 | foreach (Item childItem in this.ChildItems) | ||
| 541 | { | ||
| 542 | if (0 == childItem.ChildItems.Count) | ||
| 543 | { | ||
| 544 | flattenedChildItems.Add(childItem); | ||
| 545 | } | ||
| 546 | else | ||
| 547 | { | ||
| 548 | childItem.FlattenChildItems(); | ||
| 549 | flattenedChildItems.Add(childItem.ChildItems); | ||
| 550 | } | ||
| 551 | } | ||
| 552 | |||
| 553 | this.ChildItems.Clear(); | ||
| 554 | this.ChildItems.Add(flattenedChildItems); | ||
| 555 | } | ||
| 556 | |||
| 557 | /// <summary> | ||
| 558 | /// Adds a list of items to the 'after' ordering collection. | ||
| 559 | /// </summary> | ||
| 560 | /// <param name="items">List of items to add.</param> | ||
| 561 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
| 562 | public void AddAfter(ItemCollection items, IMessaging messageHandler) | ||
| 563 | { | ||
| 564 | foreach (Item item in items) | ||
| 565 | { | ||
| 566 | this.AddAfter(item, messageHandler); | ||
| 567 | } | ||
| 568 | } | ||
| 569 | |||
| 570 | /// <summary> | ||
| 571 | /// Adds an item to the 'after' ordering collection. | ||
| 572 | /// </summary> | ||
| 573 | /// <param name="after">Item to add.</param> | ||
| 574 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
| 575 | public void AddAfter(Item after, IMessaging messageHandler) | ||
| 576 | { | ||
| 577 | if (this.beforeItems.Contains(after)) | ||
| 578 | { | ||
| 579 | // We could try to chain this up (the way that group circular dependencies | ||
| 580 | // are reported), but since we're in the process of flattening, we may already | ||
| 581 | // have lost some distinction between authored and propagated ordering. | ||
| 582 | string circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3} -> {0}:{1}", | ||
| 583 | this.Type, this.Id, after.Type, after.Id); | ||
| 584 | messageHandler.Write(ErrorMessages.OrderingReferenceLoopDetected(after.Row.SourceLineNumbers, circularReference)); | ||
| 585 | return; | ||
| 586 | } | ||
| 587 | |||
| 588 | this.afterItems.Add(after); | ||
| 589 | after.beforeItems.Add(this); | ||
| 590 | } | ||
| 591 | |||
| 592 | /// <summary> | ||
| 593 | /// Propagates 'after' dependencies from an item to its child items. | ||
| 594 | /// </summary> | ||
| 595 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
| 596 | /// <remarks>Because items don't know about their parent groups (and can, in fact, be in more | ||
| 597 | /// than one group at a time), we need to propagate the 'afters' from each parent item to its children | ||
| 598 | /// before we attempt to flatten the ordering.</remarks> | ||
| 599 | public void PropagateAfterToChildItems(IMessaging messageHandler) | ||
| 600 | { | ||
| 601 | if (this.ShouldItemPropagateChildOrdering()) | ||
| 602 | { | ||
| 603 | foreach (Item childItem in this.ChildItems) | ||
| 604 | { | ||
| 605 | childItem.AddAfter(this.afterItems, messageHandler); | ||
| 606 | } | ||
| 607 | } | ||
| 608 | } | ||
| 609 | |||
| 610 | /// <summary> | ||
| 611 | /// Flattens the ordering dependency for this item. | ||
| 612 | /// </summary> | ||
| 613 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
| 614 | public void FlattenAfters(IMessaging messageHandler) | ||
| 615 | { | ||
| 616 | if (this.flattenedAfterItems) | ||
| 617 | { | ||
| 618 | return; | ||
| 619 | } | ||
| 620 | |||
| 621 | this.flattenedAfterItems = true; | ||
| 622 | |||
| 623 | // Ensure that if we're after something (A), and *it's* after something (B), | ||
| 624 | // that we list ourselved as after both (A) *and* (B). | ||
| 625 | ItemCollection nestedAfterItems = new ItemCollection(); | ||
| 626 | |||
| 627 | foreach (Item afterItem in this.afterItems) | ||
| 628 | { | ||
| 629 | afterItem.FlattenAfters(messageHandler); | ||
| 630 | nestedAfterItems.Add(afterItem.afterItems); | ||
| 631 | |||
| 632 | if (afterItem.ShouldItemPropagateChildOrdering()) | ||
| 633 | { | ||
| 634 | // If we are after a group, it really means | ||
| 635 | // we are after all of the group's children. | ||
| 636 | foreach (Item childItem in afterItem.ChildItems) | ||
| 637 | { | ||
| 638 | childItem.FlattenAfters(messageHandler); | ||
| 639 | nestedAfterItems.Add(childItem.afterItems); | ||
| 640 | nestedAfterItems.Add(childItem); | ||
| 641 | } | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | this.AddAfter(nestedAfterItems, messageHandler); | ||
| 646 | } | ||
| 647 | |||
| 648 | // We *don't* propagate ordering information from Packages or | ||
| 649 | // Containers to their children, because ordering doesn't matter | ||
| 650 | // for them, and a Payload in two Packages (or Containers) can | ||
| 651 | // cause a circular reference to occur. | ||
| 652 | private bool ShouldItemPropagateChildOrdering() | ||
| 653 | { | ||
| 654 | if (String.Equals(nameof(ComplexReferenceParentType.Package), this.Type, StringComparison.Ordinal) || | ||
| 655 | String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal)) | ||
| 656 | { | ||
| 657 | return false; | ||
| 658 | } | ||
| 659 | return true; | ||
| 660 | } | ||
| 661 | |||
| 662 | /// <summary> | ||
| 663 | /// Helper IComparer class to make ordering easier. | ||
| 664 | /// </summary> | ||
| 665 | internal class AfterItemComparer : IComparer<Item> | ||
| 666 | { | ||
| 667 | public int Compare(Item x, Item y) | ||
| 668 | { | ||
| 669 | if (x.afterItems.Contains(y)) | ||
| 670 | { | ||
| 671 | return 1; | ||
| 672 | } | ||
| 673 | else if (y.afterItems.Contains(x)) | ||
| 674 | { | ||
| 675 | return -1; | ||
| 676 | } | ||
| 677 | |||
| 678 | return String.CompareOrdinal(x.Id, y.Id); | ||
| 679 | } | ||
| 680 | } | ||
| 681 | } | ||
| 682 | } | ||
| 683 | } | ||
diff --git a/src/wix/WixToolset.Core/LinkContext.cs b/src/wix/WixToolset.Core/LinkContext.cs new file mode 100644 index 00000000..b99bb9c4 --- /dev/null +++ b/src/wix/WixToolset.Core/LinkContext.cs | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Threading; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | |||
| 12 | internal class LinkContext : ILinkContext | ||
| 13 | { | ||
| 14 | internal LinkContext(IServiceProvider serviceProvider) | ||
| 15 | { | ||
| 16 | this.ServiceProvider = serviceProvider; | ||
| 17 | } | ||
| 18 | |||
| 19 | public IServiceProvider ServiceProvider { get; } | ||
| 20 | |||
| 21 | public IReadOnlyCollection<ILinkerExtension> Extensions { get; set; } | ||
| 22 | |||
| 23 | public IReadOnlyCollection<IExtensionData> ExtensionData { get; set; } | ||
| 24 | |||
| 25 | public OutputType ExpectedOutputType { get; set; } | ||
| 26 | |||
| 27 | public IReadOnlyCollection<Intermediate> Intermediates { get; set; } | ||
| 28 | |||
| 29 | public ISymbolDefinitionCreator SymbolDefinitionCreator { get; set; } | ||
| 30 | |||
| 31 | public CancellationToken CancellationToken { get; set; } | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/wix/WixToolset.Core/Linker.cs b/src/wix/WixToolset.Core/Linker.cs new file mode 100644 index 00000000..47671f26 --- /dev/null +++ b/src/wix/WixToolset.Core/Linker.cs | |||
| @@ -0,0 +1,942 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Diagnostics; | ||
| 9 | using System.Globalization; | ||
| 10 | using System.Linq; | ||
| 11 | using WixToolset.Core.Link; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Data.WindowsInstaller; | ||
| 15 | using WixToolset.Extensibility.Data; | ||
| 16 | using WixToolset.Extensibility.Services; | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// Linker core of the WiX toolset. | ||
| 20 | /// </summary> | ||
| 21 | internal class Linker : ILinker | ||
| 22 | { | ||
| 23 | private static readonly string EmptyGuid = Guid.Empty.ToString("B"); | ||
| 24 | |||
| 25 | private readonly bool sectionIdOnRows; | ||
| 26 | |||
| 27 | /// <summary> | ||
| 28 | /// Creates a linker. | ||
| 29 | /// </summary> | ||
| 30 | internal Linker(IServiceProvider serviceProvider) | ||
| 31 | { | ||
| 32 | this.ServiceProvider = serviceProvider; | ||
| 33 | this.Messaging = this.ServiceProvider.GetService<IMessaging>(); | ||
| 34 | this.sectionIdOnRows = true; // TODO: what is the correct value for this? | ||
| 35 | } | ||
| 36 | |||
| 37 | private IServiceProvider ServiceProvider { get; } | ||
| 38 | |||
| 39 | private IMessaging Messaging { get; } | ||
| 40 | |||
| 41 | private ILinkContext Context { get; set; } | ||
| 42 | |||
| 43 | /// <summary> | ||
| 44 | /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output. | ||
| 45 | /// </summary> | ||
| 46 | /// <value>The path to output the xml file.</value> | ||
| 47 | public string UnreferencedSymbolsFile { get; set; } | ||
| 48 | |||
| 49 | /// <summary> | ||
| 50 | /// Gets or sets the option to show pedantic messages. | ||
| 51 | /// </summary> | ||
| 52 | /// <value>The option to show pedantic messages.</value> | ||
| 53 | public bool ShowPedanticMessages { get; set; } | ||
| 54 | |||
| 55 | /// <summary> | ||
| 56 | /// Links a collection of sections into an output. | ||
| 57 | /// </summary> | ||
| 58 | /// <returns>Output intermediate from the linking.</returns> | ||
| 59 | public Intermediate Link(ILinkContext context) | ||
| 60 | { | ||
| 61 | this.Context = context; | ||
| 62 | |||
| 63 | if (this.Context.SymbolDefinitionCreator == null) | ||
| 64 | { | ||
| 65 | this.Context.SymbolDefinitionCreator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>(); | ||
| 66 | } | ||
| 67 | |||
| 68 | foreach (var extension in this.Context.Extensions) | ||
| 69 | { | ||
| 70 | extension.PreLink(this.Context); | ||
| 71 | } | ||
| 72 | |||
| 73 | var invalidIntermediates = this.Context.Intermediates.Where(i => !i.HasLevel(Data.IntermediateLevels.Compiled)); | ||
| 74 | if (invalidIntermediates.Any()) | ||
| 75 | { | ||
| 76 | this.Messaging.Write(ErrorMessages.IntermediatesMustBeCompiled(String.Join(", ", invalidIntermediates.Select(i => i.Id)))); | ||
| 77 | } | ||
| 78 | |||
| 79 | Intermediate intermediate = null; | ||
| 80 | try | ||
| 81 | { | ||
| 82 | var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList(); | ||
| 83 | var localizations = this.Context.Intermediates.SelectMany(i => i.Localizations).ToList(); | ||
| 84 | |||
| 85 | // Add sections from the extensions with data. | ||
| 86 | foreach (var data in this.Context.ExtensionData) | ||
| 87 | { | ||
| 88 | var library = data.GetLibrary(this.Context.SymbolDefinitionCreator); | ||
| 89 | |||
| 90 | if (library != null) | ||
| 91 | { | ||
| 92 | sections.AddRange(library.Sections); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | //this.activeOutput = null; | ||
| 97 | |||
| 98 | var multipleFeatureComponents = new Hashtable(); | ||
| 99 | |||
| 100 | var wixVariables = new Dictionary<string, WixVariableSymbol>(); | ||
| 101 | |||
| 102 | // First find the entry section and while processing all sections load all the symbols from all of the sections. | ||
| 103 | var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType); | ||
| 104 | find.Execute(); | ||
| 105 | |||
| 106 | // Must have found the entry section by now. | ||
| 107 | if (null == find.EntrySection) | ||
| 108 | { | ||
| 109 | if (this.Context.ExpectedOutputType == OutputType.IntermediatePostLink || this.Context.ExpectedOutputType == OutputType.Unknown) | ||
| 110 | { | ||
| 111 | throw new WixException(ErrorMessages.MissingEntrySection()); | ||
| 112 | } | ||
| 113 | else | ||
| 114 | { | ||
| 115 | throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString())); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | // Add the missing standard action and directory symbols. | ||
| 120 | this.LoadStandardSymbols(find.SymbolsByName); | ||
| 121 | |||
| 122 | // Resolve the symbol references to find the set of sections we care about for linking. | ||
| 123 | // Of course, we start with the entry section (that's how it got its name after all). | ||
| 124 | var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName); | ||
| 125 | resolve.Execute(); | ||
| 126 | |||
| 127 | if (this.Messaging.EncounteredError) | ||
| 128 | { | ||
| 129 | return null; | ||
| 130 | } | ||
| 131 | |||
| 132 | // Reset the sections to only those that were resolved then flatten the complex | ||
| 133 | // references that particpate in groups. | ||
| 134 | sections = resolve.ResolvedSections.ToList(); | ||
| 135 | |||
| 136 | // TODO: consider filtering "localizations" down to only those localizations from | ||
| 137 | // intermediates in the sections. | ||
| 138 | |||
| 139 | this.FlattenSectionsComplexReferences(sections); | ||
| 140 | |||
| 141 | if (this.Messaging.EncounteredError) | ||
| 142 | { | ||
| 143 | return null; | ||
| 144 | } | ||
| 145 | |||
| 146 | // The hard part in linking is processing the complex references. | ||
| 147 | var referencedComponents = new HashSet<string>(); | ||
| 148 | var componentsToFeatures = new ConnectToFeatureCollection(); | ||
| 149 | var featuresToFeatures = new ConnectToFeatureCollection(); | ||
| 150 | var modulesToFeatures = new ConnectToFeatureCollection(); | ||
| 151 | this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures); | ||
| 152 | |||
| 153 | if (this.Messaging.EncounteredError) | ||
| 154 | { | ||
| 155 | return null; | ||
| 156 | } | ||
| 157 | |||
| 158 | // Display an error message for Components that were not referenced by a Feature. | ||
| 159 | foreach (var symbolWithSection in resolve.ReferencedSymbolWithSections.Where(s => s.Symbol.Definition.Type == SymbolDefinitionType.Component)) | ||
| 160 | { | ||
| 161 | if (!referencedComponents.Contains(symbolWithSection.Name)) | ||
| 162 | { | ||
| 163 | this.Messaging.Write(ErrorMessages.OrphanedComponent(symbolWithSection.Symbol.SourceLineNumbers, symbolWithSection.Symbol.Id.Id)); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | // Report duplicates that would ultimately end up being primary key collisions. | ||
| 168 | { | ||
| 169 | var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections); | ||
| 170 | reportDupes.Execute(); | ||
| 171 | } | ||
| 172 | |||
| 173 | if (this.Messaging.EncounteredError) | ||
| 174 | { | ||
| 175 | return null; | ||
| 176 | } | ||
| 177 | |||
| 178 | // resolve the feature to feature connects | ||
| 179 | this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.SymbolsByName); | ||
| 180 | |||
| 181 | // Create a new section to hold the linked content. Start with the entry section's | ||
| 182 | // metadata. | ||
| 183 | var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type); | ||
| 184 | |||
| 185 | var sectionCount = 0; | ||
| 186 | |||
| 187 | foreach (var section in sections) | ||
| 188 | { | ||
| 189 | sectionCount++; | ||
| 190 | |||
| 191 | var sectionId = section.Id; | ||
| 192 | if (null == sectionId && this.sectionIdOnRows) | ||
| 193 | { | ||
| 194 | sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture); | ||
| 195 | } | ||
| 196 | |||
| 197 | foreach (var symbol in section.Symbols) | ||
| 198 | { | ||
| 199 | if (find.RedundantSymbols.Contains(symbol)) | ||
| 200 | { | ||
| 201 | continue; | ||
| 202 | } | ||
| 203 | |||
| 204 | var copySymbol = true; // by default, copy symbols. | ||
| 205 | |||
| 206 | // handle special tables | ||
| 207 | switch (symbol.Definition.Type) | ||
| 208 | { | ||
| 209 | case SymbolDefinitionType.Class: | ||
| 210 | if (SectionType.Product == resolvedSection.Type) | ||
| 211 | { | ||
| 212 | this.ResolveFeatures(symbol, (int)ClassSymbolFields.ComponentRef, (int)ClassSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); | ||
| 213 | } | ||
| 214 | break; | ||
| 215 | |||
| 216 | case SymbolDefinitionType.Extension: | ||
| 217 | if (SectionType.Product == resolvedSection.Type) | ||
| 218 | { | ||
| 219 | this.ResolveFeatures(symbol, (int)ExtensionSymbolFields.ComponentRef, (int)ExtensionSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); | ||
| 220 | } | ||
| 221 | break; | ||
| 222 | |||
| 223 | case SymbolDefinitionType.Assembly: | ||
| 224 | if (SectionType.Product == resolvedSection.Type) | ||
| 225 | { | ||
| 226 | this.ResolveFeatures(symbol, (int)AssemblySymbolFields.ComponentRef, (int)AssemblySymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); | ||
| 227 | } | ||
| 228 | break; | ||
| 229 | |||
| 230 | case SymbolDefinitionType.PublishComponent: | ||
| 231 | if (SectionType.Product == resolvedSection.Type) | ||
| 232 | { | ||
| 233 | this.ResolveFeatures(symbol, (int)PublishComponentSymbolFields.ComponentRef, (int)PublishComponentSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); | ||
| 234 | } | ||
| 235 | break; | ||
| 236 | |||
| 237 | case SymbolDefinitionType.Shortcut: | ||
| 238 | if (SectionType.Product == resolvedSection.Type) | ||
| 239 | { | ||
| 240 | this.ResolveFeatures(symbol, (int)ShortcutSymbolFields.ComponentRef, (int)ShortcutSymbolFields.Target, componentsToFeatures, multipleFeatureComponents); | ||
| 241 | } | ||
| 242 | break; | ||
| 243 | |||
| 244 | case SymbolDefinitionType.TypeLib: | ||
| 245 | if (SectionType.Product == resolvedSection.Type) | ||
| 246 | { | ||
| 247 | this.ResolveFeatures(symbol, (int)TypeLibSymbolFields.ComponentRef, (int)TypeLibSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); | ||
| 248 | } | ||
| 249 | break; | ||
| 250 | |||
| 251 | case SymbolDefinitionType.WixMerge: | ||
| 252 | if (SectionType.Product == resolvedSection.Type) | ||
| 253 | { | ||
| 254 | this.ResolveFeatures(symbol, -1, (int)WixMergeSymbolFields.FeatureRef, modulesToFeatures, null); | ||
| 255 | } | ||
| 256 | break; | ||
| 257 | |||
| 258 | case SymbolDefinitionType.WixComplexReference: | ||
| 259 | copySymbol = false; | ||
| 260 | break; | ||
| 261 | |||
| 262 | case SymbolDefinitionType.WixSimpleReference: | ||
| 263 | copySymbol = false; | ||
| 264 | break; | ||
| 265 | |||
| 266 | case SymbolDefinitionType.WixVariable: | ||
| 267 | this.AddWixVariable(wixVariables, (WixVariableSymbol)symbol); | ||
| 268 | copySymbol = false; // Do not copy the symbol, it will be added later after all overriding has been handled. | ||
| 269 | break; | ||
| 270 | } | ||
| 271 | |||
| 272 | if (copySymbol) | ||
| 273 | { | ||
| 274 | resolvedSection.AddSymbol(symbol); | ||
| 275 | } | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | // Copy the module to feature connections into the output. | ||
| 280 | foreach (ConnectToFeature connectToFeature in modulesToFeatures) | ||
| 281 | { | ||
| 282 | foreach (var feature in connectToFeature.ConnectFeatures) | ||
| 283 | { | ||
| 284 | resolvedSection.AddSymbol(new WixFeatureModulesSymbol | ||
| 285 | { | ||
| 286 | FeatureRef = feature, | ||
| 287 | WixMergeRef = connectToFeature.ChildId | ||
| 288 | }); | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | // Correct the section Id in FeatureComponents table. | ||
| 293 | if (this.sectionIdOnRows) | ||
| 294 | { | ||
| 295 | #if TODO_DO_SYMBOLS_NEED_SECTIONIDS | ||
| 296 | var componentSectionIds = resolvedSection.Symbols.OfType<ComponentSymbol>().ToDictionary(c => c.Id.Id, c => c.SectionId); | ||
| 297 | |||
| 298 | foreach (var featureComponentSymbol in resolvedSection.Symbols.OfType<FeatureComponentsSymbol>()) | ||
| 299 | { | ||
| 300 | if (componentSectionIds.TryGetValue(featureComponentSymbol.ComponentRef, out var componentSectionId)) | ||
| 301 | { | ||
| 302 | featureComponentSymbol.SectionId = componentSectionId; | ||
| 303 | } | ||
| 304 | } | ||
| 305 | #endif | ||
| 306 | } | ||
| 307 | |||
| 308 | // Copy the wix variable rows to the output now that all overriding has been accounted for. | ||
| 309 | foreach (var symbol in wixVariables.Values) | ||
| 310 | { | ||
| 311 | resolvedSection.AddSymbol(symbol); | ||
| 312 | } | ||
| 313 | |||
| 314 | // Bundles have groups of data that must be flattened in a way different from other types. | ||
| 315 | if (resolvedSection.Type == SectionType.Bundle) | ||
| 316 | { | ||
| 317 | var command = new FlattenAndProcessBundleTablesCommand(resolvedSection, this.Messaging); | ||
| 318 | command.Execute(); | ||
| 319 | } | ||
| 320 | |||
| 321 | if (this.Messaging.EncounteredError) | ||
| 322 | { | ||
| 323 | return null; | ||
| 324 | } | ||
| 325 | |||
| 326 | var collate = new CollateLocalizationsCommand(this.Messaging, localizations); | ||
| 327 | var localizationsByCulture = collate.Execute(); | ||
| 328 | |||
| 329 | intermediate = new Intermediate(resolvedSection.Id, Data.IntermediateLevels.Linked, new[] { resolvedSection }, localizationsByCulture); | ||
| 330 | } | ||
| 331 | finally | ||
| 332 | { | ||
| 333 | foreach (var extension in this.Context.Extensions) | ||
| 334 | { | ||
| 335 | extension.PostLink(intermediate); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | return this.Messaging.EncounteredError ? null : intermediate; | ||
| 340 | } | ||
| 341 | |||
| 342 | /// <summary> | ||
| 343 | /// Check for colliding values and collect the wix variable rows. | ||
| 344 | /// </summary> | ||
| 345 | /// <param name="wixVariables">Collection of WixVariableSymbols by id.</param> | ||
| 346 | /// <param name="symbol">WixVariableSymbol to add, if not overridden.</param> | ||
| 347 | private void AddWixVariable(Dictionary<string, WixVariableSymbol> wixVariables, WixVariableSymbol symbol) | ||
| 348 | { | ||
| 349 | var id = symbol.Id.Id; | ||
| 350 | |||
| 351 | if (wixVariables.TryGetValue(id, out var collidingSymbol)) | ||
| 352 | { | ||
| 353 | if (collidingSymbol.Overridable && !symbol.Overridable) | ||
| 354 | { | ||
| 355 | wixVariables[id] = symbol; | ||
| 356 | } | ||
| 357 | else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable)) | ||
| 358 | { | ||
| 359 | this.Messaging.Write(ErrorMessages.WixVariableCollision(symbol.SourceLineNumbers, id)); | ||
| 360 | } | ||
| 361 | } | ||
| 362 | else | ||
| 363 | { | ||
| 364 | wixVariables.Add(id, symbol); | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | /// <summary> | ||
| 369 | /// Load the standard action and directory symbols. | ||
| 370 | /// </summary> | ||
| 371 | /// <param name="symbolsByName">Collection of symbols.</param> | ||
| 372 | private void LoadStandardSymbols(IDictionary<string, SymbolWithSection> symbolsByName) | ||
| 373 | { | ||
| 374 | foreach (var actionSymbol in WindowsInstallerStandard.StandardActions()) | ||
| 375 | { | ||
| 376 | var symbolWithSection = new SymbolWithSection(null, actionSymbol); | ||
| 377 | |||
| 378 | // If the action's symbol has not already been defined (i.e. overriden by the user), add it now. | ||
| 379 | if (!symbolsByName.ContainsKey(symbolWithSection.Name)) | ||
| 380 | { | ||
| 381 | symbolsByName.Add(symbolWithSection.Name, symbolWithSection); | ||
| 382 | } | ||
| 383 | } | ||
| 384 | |||
| 385 | foreach (var directorySymbol in WindowsInstallerStandard.StandardDirectories()) | ||
| 386 | { | ||
| 387 | var symbolWithSection = new SymbolWithSection(null, directorySymbol); | ||
| 388 | |||
| 389 | // If the directory's symbol has not already been defined (i.e. overriden by the user), add it now. | ||
| 390 | if (!symbolsByName.ContainsKey(symbolWithSection.Name)) | ||
| 391 | { | ||
| 392 | symbolsByName.Add(symbolWithSection.Name, symbolWithSection); | ||
| 393 | } | ||
| 394 | } | ||
| 395 | } | ||
| 396 | |||
| 397 | /// <summary> | ||
| 398 | /// Process the complex references. | ||
| 399 | /// </summary> | ||
| 400 | /// <param name="resolvedSection">Active section to add symbols to.</param> | ||
| 401 | /// <param name="sections">Sections that are referenced during the link process.</param> | ||
| 402 | /// <param name="referencedComponents">Collection of all components referenced by complex reference.</param> | ||
| 403 | /// <param name="componentsToFeatures">Component to feature complex references.</param> | ||
| 404 | /// <param name="featuresToFeatures">Feature to feature complex references.</param> | ||
| 405 | /// <param name="modulesToFeatures">Module to feature complex references.</param> | ||
| 406 | private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnumerable<IntermediateSection> sections, ISet<string> referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures) | ||
| 407 | { | ||
| 408 | var componentsToModules = new Hashtable(); | ||
| 409 | |||
| 410 | foreach (var section in sections) | ||
| 411 | { | ||
| 412 | // Need ToList since we might want to add symbols while processing. | ||
| 413 | foreach (var wixComplexReferenceRow in section.Symbols.OfType<WixComplexReferenceSymbol>().ToList()) | ||
| 414 | { | ||
| 415 | ConnectToFeature connection; | ||
| 416 | switch (wixComplexReferenceRow.ParentType) | ||
| 417 | { | ||
| 418 | case ComplexReferenceParentType.Feature: | ||
| 419 | switch (wixComplexReferenceRow.ChildType) | ||
| 420 | { | ||
| 421 | case ComplexReferenceChildType.Component: | ||
| 422 | connection = componentsToFeatures[wixComplexReferenceRow.Child]; | ||
| 423 | if (null == connection) | ||
| 424 | { | ||
| 425 | componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); | ||
| 426 | } | ||
| 427 | else if (wixComplexReferenceRow.IsPrimary) | ||
| 428 | { | ||
| 429 | if (connection.IsExplicitPrimaryFeature) | ||
| 430 | { | ||
| 431 | this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), connection.PrimaryFeature ?? resolvedSection.Id)); | ||
| 432 | continue; | ||
| 433 | } | ||
| 434 | else | ||
| 435 | { | ||
| 436 | connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects | ||
| 437 | connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature | ||
| 438 | connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again | ||
| 439 | } | ||
| 440 | } | ||
| 441 | else | ||
| 442 | { | ||
| 443 | connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); | ||
| 444 | } | ||
| 445 | |||
| 446 | // add a row to the FeatureComponents table | ||
| 447 | section.AddSymbol(new FeatureComponentsSymbol | ||
| 448 | { | ||
| 449 | FeatureRef = wixComplexReferenceRow.Parent, | ||
| 450 | ComponentRef = wixComplexReferenceRow.Child, | ||
| 451 | }); | ||
| 452 | |||
| 453 | // index the component for finding orphaned records | ||
| 454 | var symbolName = String.Concat("Component:", wixComplexReferenceRow.Child); | ||
| 455 | referencedComponents.Add(symbolName); | ||
| 456 | |||
| 457 | break; | ||
| 458 | |||
| 459 | case ComplexReferenceChildType.Feature: | ||
| 460 | connection = featuresToFeatures[wixComplexReferenceRow.Child]; | ||
| 461 | if (null != connection) | ||
| 462 | { | ||
| 463 | this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); | ||
| 464 | continue; | ||
| 465 | } | ||
| 466 | |||
| 467 | featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); | ||
| 468 | break; | ||
| 469 | |||
| 470 | case ComplexReferenceChildType.Module: | ||
| 471 | connection = modulesToFeatures[wixComplexReferenceRow.Child]; | ||
| 472 | if (null == connection) | ||
| 473 | { | ||
| 474 | modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); | ||
| 475 | } | ||
| 476 | else if (wixComplexReferenceRow.IsPrimary) | ||
| 477 | { | ||
| 478 | if (connection.IsExplicitPrimaryFeature) | ||
| 479 | { | ||
| 480 | this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); | ||
| 481 | continue; | ||
| 482 | } | ||
| 483 | else | ||
| 484 | { | ||
| 485 | connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects | ||
| 486 | connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature | ||
| 487 | connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again | ||
| 488 | } | ||
| 489 | } | ||
| 490 | else | ||
| 491 | { | ||
| 492 | connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); | ||
| 493 | } | ||
| 494 | break; | ||
| 495 | |||
| 496 | default: | ||
| 497 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); | ||
| 498 | } | ||
| 499 | break; | ||
| 500 | |||
| 501 | case ComplexReferenceParentType.Module: | ||
| 502 | switch (wixComplexReferenceRow.ChildType) | ||
| 503 | { | ||
| 504 | case ComplexReferenceChildType.Component: | ||
| 505 | if (componentsToModules.ContainsKey(wixComplexReferenceRow.Child)) | ||
| 506 | { | ||
| 507 | this.Messaging.Write(ErrorMessages.ComponentReferencedTwice(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.Child)); | ||
| 508 | continue; | ||
| 509 | } | ||
| 510 | else | ||
| 511 | { | ||
| 512 | componentsToModules.Add(wixComplexReferenceRow.Child, wixComplexReferenceRow); // should always be new | ||
| 513 | |||
| 514 | // add a row to the ModuleComponents table | ||
| 515 | section.AddSymbol(new ModuleComponentsSymbol | ||
| 516 | { | ||
| 517 | Component = wixComplexReferenceRow.Child, | ||
| 518 | ModuleID = wixComplexReferenceRow.Parent, | ||
| 519 | Language = Convert.ToInt32(wixComplexReferenceRow.ParentLanguage), | ||
| 520 | }); | ||
| 521 | } | ||
| 522 | |||
| 523 | // index the component for finding orphaned records | ||
| 524 | var componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.Child); | ||
| 525 | referencedComponents.Add(componentSymbolName); | ||
| 526 | |||
| 527 | break; | ||
| 528 | |||
| 529 | default: | ||
| 530 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); | ||
| 531 | } | ||
| 532 | break; | ||
| 533 | |||
| 534 | case ComplexReferenceParentType.Patch: | ||
| 535 | switch (wixComplexReferenceRow.ChildType) | ||
| 536 | { | ||
| 537 | case ComplexReferenceChildType.PatchFamily: | ||
| 538 | case ComplexReferenceChildType.PatchFamilyGroup: | ||
| 539 | break; | ||
| 540 | |||
| 541 | default: | ||
| 542 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); | ||
| 543 | } | ||
| 544 | break; | ||
| 545 | |||
| 546 | case ComplexReferenceParentType.Product: | ||
| 547 | switch (wixComplexReferenceRow.ChildType) | ||
| 548 | { | ||
| 549 | case ComplexReferenceChildType.Feature: | ||
| 550 | connection = featuresToFeatures[wixComplexReferenceRow.Child]; | ||
| 551 | if (null != connection) | ||
| 552 | { | ||
| 553 | this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); | ||
| 554 | continue; | ||
| 555 | } | ||
| 556 | |||
| 557 | featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary)); | ||
| 558 | break; | ||
| 559 | |||
| 560 | default: | ||
| 561 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); | ||
| 562 | } | ||
| 563 | break; | ||
| 564 | |||
| 565 | default: | ||
| 566 | // Note: Groups have been processed before getting here so they are not handled by any case above. | ||
| 567 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType))); | ||
| 568 | } | ||
| 569 | } | ||
| 570 | } | ||
| 571 | } | ||
| 572 | |||
| 573 | /// <summary> | ||
| 574 | /// Flattens all complex references in all sections in the collection. | ||
| 575 | /// </summary> | ||
| 576 | /// <param name="sections">Sections that are referenced during the link process.</param> | ||
| 577 | private void FlattenSectionsComplexReferences(IEnumerable<IntermediateSection> sections) | ||
| 578 | { | ||
| 579 | var parentGroups = new Dictionary<string, List<WixComplexReferenceSymbol>>(); | ||
| 580 | var parentGroupsSections = new Dictionary<string, IntermediateSection>(); | ||
| 581 | var parentGroupsNeedingProcessing = new Dictionary<string, IntermediateSection>(); | ||
| 582 | |||
| 583 | // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections); | ||
| 584 | |||
| 585 | // Step 1: Gather all of the complex references that are going to participate | ||
| 586 | // in the flatting process. This means complex references that have "grouping | ||
| 587 | // parents" of Features, Modules, and, of course, Groups. These references | ||
| 588 | // that participate in a "grouping parent" will be removed from their section | ||
| 589 | // now and after processing added back in Step 3 below. | ||
| 590 | foreach (var section in sections) | ||
| 591 | { | ||
| 592 | var removeSymbols = new List<IntermediateSymbol>(); | ||
| 593 | |||
| 594 | foreach (var symbol in section.Symbols) | ||
| 595 | { | ||
| 596 | // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature, | ||
| 597 | // and Module. Non-grouping complex references are simple and | ||
| 598 | // resolved during normal complex reference resolutions. | ||
| 599 | if (symbol is WixComplexReferenceSymbol wixComplexReferenceRow && | ||
| 600 | (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || | ||
| 601 | ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || | ||
| 602 | ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || | ||
| 603 | ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || | ||
| 604 | ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || | ||
| 605 | ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType)) | ||
| 606 | { | ||
| 607 | var parentTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent); | ||
| 608 | |||
| 609 | // Group all complex references with a common parent | ||
| 610 | // together so we can find them quickly while processing in | ||
| 611 | // Step 2. | ||
| 612 | if (!parentGroups.TryGetValue(parentTypeAndId, out var childrenComplexRefs)) | ||
| 613 | { | ||
| 614 | childrenComplexRefs = new List<WixComplexReferenceSymbol>(); | ||
| 615 | parentGroups.Add(parentTypeAndId, childrenComplexRefs); | ||
| 616 | } | ||
| 617 | |||
| 618 | childrenComplexRefs.Add(wixComplexReferenceRow); | ||
| 619 | removeSymbols.Add(wixComplexReferenceRow); | ||
| 620 | |||
| 621 | // Remember the mapping from set of complex references with a common | ||
| 622 | // parent to their section. We'll need this to add them back to the | ||
| 623 | // correct section in Step 3. | ||
| 624 | if (!parentGroupsSections.TryGetValue(parentTypeAndId, out var parentSection)) | ||
| 625 | { | ||
| 626 | parentGroupsSections.Add(parentTypeAndId, section); | ||
| 627 | } | ||
| 628 | |||
| 629 | // If the child of the complex reference is another group, then in Step 2 | ||
| 630 | // we're going to have to process this complex reference again to copy | ||
| 631 | // the child group's references into the parent group. | ||
| 632 | if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || | ||
| 633 | (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || | ||
| 634 | (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) | ||
| 635 | { | ||
| 636 | if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)) | ||
| 637 | { | ||
| 638 | parentGroupsNeedingProcessing.Add(parentTypeAndId, section); | ||
| 639 | } | ||
| 640 | } | ||
| 641 | } | ||
| 642 | } | ||
| 643 | |||
| 644 | foreach (var removeSymbol in removeSymbols) | ||
| 645 | { | ||
| 646 | section.RemoveSymbol(removeSymbol); | ||
| 647 | } | ||
| 648 | } | ||
| 649 | |||
| 650 | Debug.Assert(parentGroups.Count == parentGroupsSections.Count); | ||
| 651 | Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count); | ||
| 652 | |||
| 653 | // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections); | ||
| 654 | |||
| 655 | // Step 2: Loop through the parent groups that have nested groups removing | ||
| 656 | // them from the hash table as they are processed. At the end of this the | ||
| 657 | // complex references should all be flattened. | ||
| 658 | var keys = parentGroupsNeedingProcessing.Keys.ToList(); | ||
| 659 | |||
| 660 | foreach (var key in keys) | ||
| 661 | { | ||
| 662 | if (parentGroupsNeedingProcessing.ContainsKey(key)) | ||
| 663 | { | ||
| 664 | var loopDetector = new Stack<string>(); | ||
| 665 | this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing); | ||
| 666 | } | ||
| 667 | else | ||
| 668 | { | ||
| 669 | // the group must have allready been procesed and removed from the hash table | ||
| 670 | } | ||
| 671 | } | ||
| 672 | Debug.Assert(0 == parentGroupsNeedingProcessing.Count); | ||
| 673 | |||
| 674 | // Step 3: Finally, ensure that all of the groups that were removed | ||
| 675 | // in Step 1 and flattened in Step 2 are added to their appropriate | ||
| 676 | // section. This is where we will toss out the final no-longer-needed | ||
| 677 | // groups. | ||
| 678 | foreach (var parentGroup in parentGroups.Keys) | ||
| 679 | { | ||
| 680 | var section = parentGroupsSections[parentGroup]; | ||
| 681 | |||
| 682 | foreach (var wixComplexReferenceRow in parentGroups[parentGroup]) | ||
| 683 | { | ||
| 684 | if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) && | ||
| 685 | (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) && | ||
| 686 | (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType)) | ||
| 687 | { | ||
| 688 | section.AddSymbol(wixComplexReferenceRow); | ||
| 689 | } | ||
| 690 | } | ||
| 691 | } | ||
| 692 | |||
| 693 | // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections); | ||
| 694 | } | ||
| 695 | |||
| 696 | private string CombineTypeAndId(ComplexReferenceParentType type, string id) | ||
| 697 | { | ||
| 698 | return String.Concat(type.ToString(), ":", id); | ||
| 699 | } | ||
| 700 | |||
| 701 | private string CombineTypeAndId(ComplexReferenceChildType type, string id) | ||
| 702 | { | ||
| 703 | return String.Concat(type.ToString(), ":", id); | ||
| 704 | } | ||
| 705 | |||
| 706 | /// <summary> | ||
| 707 | /// Recursively processes the group. | ||
| 708 | /// </summary> | ||
| 709 | /// <param name="parentTypeAndId">String combination type and id of group to process next.</param> | ||
| 710 | /// <param name="loopDetector">Stack of groups processed thus far. Used to detect loops.</param> | ||
| 711 | /// <param name="parentGroups">Hash table of complex references grouped by parent id.</param> | ||
| 712 | /// <param name="parentGroupsNeedingProcessing">Hash table of parent groups that still have nested groups that need to be flattened.</param> | ||
| 713 | private void FlattenGroup(string parentTypeAndId, Stack<string> loopDetector, Dictionary<string, List<WixComplexReferenceSymbol>> parentGroups, Dictionary<string, IntermediateSection> parentGroupsNeedingProcessing) | ||
| 714 | { | ||
| 715 | Debug.Assert(parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)); | ||
| 716 | loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying | ||
| 717 | |||
| 718 | var allNewChildComplexReferences = new List<WixComplexReferenceSymbol>(); | ||
| 719 | |||
| 720 | var referencesToParent = parentGroups[parentTypeAndId]; | ||
| 721 | foreach (var wixComplexReferenceRow in referencesToParent) | ||
| 722 | { | ||
| 723 | Debug.Assert(ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Patch == wixComplexReferenceRow.ParentType); | ||
| 724 | Debug.Assert(parentTypeAndId == this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent)); | ||
| 725 | |||
| 726 | // We are only interested processing when the child is a group. | ||
| 727 | if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || | ||
| 728 | (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || | ||
| 729 | (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) | ||
| 730 | { | ||
| 731 | var childTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.Child); | ||
| 732 | if (loopDetector.Contains(childTypeAndId)) | ||
| 733 | { | ||
| 734 | // Create a comma delimited list of the references that participate in the | ||
| 735 | // loop for the error message. Start at the bottom of the stack and work the | ||
| 736 | // way up to present the loop as a directed graph. | ||
| 737 | var loop = String.Join(" -> ", loopDetector); | ||
| 738 | |||
| 739 | this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(wixComplexReferenceRow?.SourceLineNumbers, loop)); | ||
| 740 | |||
| 741 | // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the | ||
| 742 | // exit of this method does at the end because we are exiting early. | ||
| 743 | loopDetector.Pop(); | ||
| 744 | parentGroupsNeedingProcessing.Remove(parentTypeAndId); | ||
| 745 | |||
| 746 | return; // bail | ||
| 747 | } | ||
| 748 | |||
| 749 | // Check to see if the child group still needs to be processed. If so, | ||
| 750 | // go do that so that we'll get all of that children's (and children's | ||
| 751 | // children) complex references correctly merged into our parent group. | ||
| 752 | if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId)) | ||
| 753 | { | ||
| 754 | this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing); | ||
| 755 | } | ||
| 756 | |||
| 757 | // If the child is a parent to anything (i.e. the parent has grandchildren) | ||
| 758 | // clone each of the children's complex references, repoint them to the parent | ||
| 759 | // complex reference (because we're moving references up the tree), and finally | ||
| 760 | // add the cloned child's complex reference to the list of complex references | ||
| 761 | // that we'll eventually add to the parent group. | ||
| 762 | if (parentGroups.TryGetValue(childTypeAndId, out var referencesToChild)) | ||
| 763 | { | ||
| 764 | foreach (var crefChild in referencesToChild) | ||
| 765 | { | ||
| 766 | // Only merge up the non-group items since groups are purged | ||
| 767 | // after this part of the processing anyway (cloning them would | ||
| 768 | // be a complete waste of time). | ||
| 769 | if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) || | ||
| 770 | (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) || | ||
| 771 | (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType)) | ||
| 772 | { | ||
| 773 | var crefChildClone = crefChild.Clone(); | ||
| 774 | Debug.Assert(crefChildClone.Parent == wixComplexReferenceRow.Child); | ||
| 775 | |||
| 776 | crefChildClone.Reparent(wixComplexReferenceRow); | ||
| 777 | allNewChildComplexReferences.Add(crefChildClone); | ||
| 778 | } | ||
| 779 | } | ||
| 780 | } | ||
| 781 | } | ||
| 782 | } | ||
| 783 | |||
| 784 | // Add the children group's complex references to the parent | ||
| 785 | // group. Clean out any left over groups and quietly remove any | ||
| 786 | // duplicate complex references that occurred during the merge. | ||
| 787 | referencesToParent.AddRange(allNewChildComplexReferences); | ||
| 788 | referencesToParent.Sort(ComplexReferenceComparision); | ||
| 789 | for (var i = referencesToParent.Count - 1; i >= 0; --i) | ||
| 790 | { | ||
| 791 | var wixComplexReferenceRow = referencesToParent[i]; | ||
| 792 | |||
| 793 | if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || | ||
| 794 | (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || | ||
| 795 | (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) | ||
| 796 | { | ||
| 797 | referencesToParent.RemoveAt(i); | ||
| 798 | } | ||
| 799 | else if (i > 0) | ||
| 800 | { | ||
| 801 | // Since the list is already sorted, we can find duplicates by simply | ||
| 802 | // looking at the next sibling in the list and tossing out one if they | ||
| 803 | // match. | ||
| 804 | var crefCompare = referencesToParent[i - 1]; | ||
| 805 | if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare)) | ||
| 806 | { | ||
| 807 | referencesToParent.RemoveAt(i); | ||
| 808 | } | ||
| 809 | } | ||
| 810 | } | ||
| 811 | |||
| 812 | int ComplexReferenceComparision(WixComplexReferenceSymbol x, WixComplexReferenceSymbol y) | ||
| 813 | { | ||
| 814 | var comparison = x.ChildType - y.ChildType; | ||
| 815 | if (0 == comparison) | ||
| 816 | { | ||
| 817 | comparison = String.Compare(x.Child, y.Child, StringComparison.Ordinal); | ||
| 818 | if (0 == comparison) | ||
| 819 | { | ||
| 820 | comparison = x.ParentType - y.ParentType; | ||
| 821 | if (0 == comparison) | ||
| 822 | { | ||
| 823 | comparison = String.Compare(x.ParentLanguage ?? String.Empty, y.ParentLanguage ?? String.Empty, StringComparison.Ordinal); | ||
| 824 | if (0 == comparison) | ||
| 825 | { | ||
| 826 | comparison = String.Compare(x.Parent, y.Parent, StringComparison.Ordinal); | ||
| 827 | } | ||
| 828 | } | ||
| 829 | } | ||
| 830 | } | ||
| 831 | |||
| 832 | return comparison; | ||
| 833 | } | ||
| 834 | |||
| 835 | loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here | ||
| 836 | parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference | ||
| 837 | } | ||
| 838 | |||
| 839 | /* | ||
| 840 | /// <summary> | ||
| 841 | /// Debugging method for displaying the section complex references. | ||
| 842 | /// </summary> | ||
| 843 | /// <param name="header">The header.</param> | ||
| 844 | /// <param name="sections">The sections to display.</param> | ||
| 845 | private void DisplaySectionComplexReferences(string header, SectionCollection sections) | ||
| 846 | { | ||
| 847 | Console.WriteLine(header); | ||
| 848 | foreach (Section section in sections) | ||
| 849 | { | ||
| 850 | Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; | ||
| 851 | |||
| 852 | foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows) | ||
| 853 | { | ||
| 854 | Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary); | ||
| 855 | } | ||
| 856 | } | ||
| 857 | } | ||
| 858 | */ | ||
| 859 | |||
| 860 | /// <summary> | ||
| 861 | /// Resolves the features connected to other features in the active output. | ||
| 862 | /// </summary> | ||
| 863 | /// <param name="featuresToFeatures">Feature to feature complex references.</param> | ||
| 864 | /// <param name="allSymbols">All symbols loaded from the sections.</param> | ||
| 865 | private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary<string, SymbolWithSection> allSymbols) | ||
| 866 | { | ||
| 867 | foreach (ConnectToFeature connection in featuresToFeatures) | ||
| 868 | { | ||
| 869 | var wixSimpleReferenceRow = new WixSimpleReferenceSymbol | ||
| 870 | { | ||
| 871 | Table = "Feature", | ||
| 872 | PrimaryKeys = connection.ChildId | ||
| 873 | }; | ||
| 874 | |||
| 875 | if (allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbol)) | ||
| 876 | { | ||
| 877 | var featureSymbol = (FeatureSymbol)symbol.Symbol; | ||
| 878 | featureSymbol.ParentFeatureRef = connection.PrimaryFeature; | ||
| 879 | } | ||
| 880 | } | ||
| 881 | } | ||
| 882 | |||
| 883 | /// <summary> | ||
| 884 | /// Resolve features for columns that have null guid placeholders. | ||
| 885 | /// </summary> | ||
| 886 | /// <param name="symbol">Symbol to resolve.</param> | ||
| 887 | /// <param name="connectionColumn">Number of the column containing the connection identifier.</param> | ||
| 888 | /// <param name="featureColumn">Number of the column containing the feature.</param> | ||
| 889 | /// <param name="connectToFeatures">Connect to feature complex references.</param> | ||
| 890 | /// <param name="multipleFeatureComponents">Hashtable of known components under multiple features.</param> | ||
| 891 | private void ResolveFeatures(IntermediateSymbol symbol, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents) | ||
| 892 | { | ||
| 893 | var connectionId = connectionColumn < 0 ? symbol.Id.Id : symbol.AsString(connectionColumn); | ||
| 894 | var featureId = symbol.AsString(featureColumn); | ||
| 895 | |||
| 896 | if (EmptyGuid == featureId) | ||
| 897 | { | ||
| 898 | var connection = connectToFeatures[connectionId]; | ||
| 899 | |||
| 900 | if (null == connection) | ||
| 901 | { | ||
| 902 | // display an error for the component or merge module as appropriate | ||
| 903 | if (null != multipleFeatureComponents) | ||
| 904 | { | ||
| 905 | this.Messaging.Write(ErrorMessages.ComponentExpectedFeature(symbol.SourceLineNumbers, connectionId, symbol.Definition.Name, symbol.Id.Id)); | ||
| 906 | } | ||
| 907 | else | ||
| 908 | { | ||
| 909 | this.Messaging.Write(ErrorMessages.MergeModuleExpectedFeature(symbol.SourceLineNumbers, connectionId)); | ||
| 910 | } | ||
| 911 | } | ||
| 912 | else | ||
| 913 | { | ||
| 914 | // check for unique, implicit, primary feature parents with multiple possible parent features | ||
| 915 | if (this.ShowPedanticMessages && | ||
| 916 | !connection.IsExplicitPrimaryFeature && | ||
| 917 | 0 < connection.ConnectFeatures.Count) | ||
| 918 | { | ||
| 919 | // display a warning for the component or merge module as approrpriate | ||
| 920 | if (null != multipleFeatureComponents) | ||
| 921 | { | ||
| 922 | if (!multipleFeatureComponents.Contains(connectionId)) | ||
| 923 | { | ||
| 924 | this.Messaging.Write(WarningMessages.ImplicitComponentPrimaryFeature(connectionId)); | ||
| 925 | |||
| 926 | // remember this component so only one warning is generated for it | ||
| 927 | multipleFeatureComponents[connectionId] = null; | ||
| 928 | } | ||
| 929 | } | ||
| 930 | else | ||
| 931 | { | ||
| 932 | this.Messaging.Write(WarningMessages.ImplicitMergeModulePrimaryFeature(connectionId)); | ||
| 933 | } | ||
| 934 | } | ||
| 935 | |||
| 936 | // set the feature | ||
| 937 | symbol.Set(featureColumn, connection.PrimaryFeature); | ||
| 938 | } | ||
| 939 | } | ||
| 940 | } | ||
| 941 | } | ||
| 942 | } | ||
diff --git a/src/wix/WixToolset.Core/LinkerErrors.cs b/src/wix/WixToolset.Core/LinkerErrors.cs new file mode 100644 index 00000000..7ce8c00e --- /dev/null +++ b/src/wix/WixToolset.Core/LinkerErrors.cs | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | internal static class LinkerErrors | ||
| 8 | { | ||
| 9 | public static Message OrphanedPayload(SourceLineNumber sourceLineNumbers, string payloadId) | ||
| 10 | { | ||
| 11 | return Message(sourceLineNumbers, Ids.OrphanedPayload, "Found orphaned Payload '{0}'. Make sure to reference it from a Package, the BootstrapperApplication, or the Bundle or move it into its own Fragment so it only gets linked in when actually used.", payloadId); | ||
| 12 | } | ||
| 13 | |||
| 14 | public static Message PackageInMultipleContainers(SourceLineNumber sourceLineNumbers, string packageId, string containerId1, string containerId2) | ||
| 15 | { | ||
| 16 | return Message(sourceLineNumbers, Ids.PackageInMultipleContainers, "The Package '{0}' is referenced from multiple containers - Container '{1}' and Container '{2}'. This is not currently supported.", packageId, containerId1, containerId2); | ||
| 17 | } | ||
| 18 | |||
| 19 | public static Message PayloadSharedWithBA(SourceLineNumber sourceLineNumbers, string payloadId) | ||
| 20 | { | ||
| 21 | return Message(sourceLineNumbers, Ids.PayloadSharedWithBA, "The Payload '{0}' is shared with the BootstrapperApplication. This is not currently supported.", payloadId); | ||
| 22 | } | ||
| 23 | |||
| 24 | public static Message UnscheduledChainPackage(SourceLineNumber sourceLineNumbers, string packageId) | ||
| 25 | { | ||
| 26 | return Message(sourceLineNumbers, Ids.UnscheduledChainPackage, "Found orphaned Package '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", packageId); | ||
| 27 | } | ||
| 28 | |||
| 29 | public static Message UnscheduledRollbackBoundary(SourceLineNumber sourceLineNumbers, string rollbackBoundaryId) | ||
| 30 | { | ||
| 31 | return Message(sourceLineNumbers, Ids.UnscheduledRollbackBoundary, "Found orphaned RollbackBoundary '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", rollbackBoundaryId); | ||
| 32 | } | ||
| 33 | |||
| 34 | private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) | ||
| 35 | { | ||
| 36 | return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); | ||
| 37 | } | ||
| 38 | |||
| 39 | public enum Ids | ||
| 40 | { | ||
| 41 | OrphanedPayload = 7000, | ||
| 42 | PackageInMultipleContainers = 7001, | ||
| 43 | PayloadSharedWithBA = 7002, | ||
| 44 | UnscheduledChainPackage = 7003, | ||
| 45 | UnscheduledRollbackBoundary = 7004, | ||
| 46 | } // last available is 7099. 7100 is WindowsInstallerBackendWarnings. | ||
| 47 | } | ||
| 48 | } | ||
diff --git a/src/wix/WixToolset.Core/LinkerWarnings.cs b/src/wix/WixToolset.Core/LinkerWarnings.cs new file mode 100644 index 00000000..968fa4ea --- /dev/null +++ b/src/wix/WixToolset.Core/LinkerWarnings.cs | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | internal static class LinkerWarnings | ||
| 8 | { | ||
| 9 | public static Message LayoutPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) | ||
| 10 | { | ||
| 11 | return Message(sourceLineNumbers, Ids.LayoutPayloadInContainer, "The layout-only Payload '{0}' is being added to Container '{1}'. It will not be extracted during layout.", payloadId, containerId); | ||
| 12 | } | ||
| 13 | |||
| 14 | public static Message PayloadInMultipleContainers(SourceLineNumber sourceLineNumbers, string payloadId, string containerId1, string containerId2) | ||
| 15 | { | ||
| 16 | return Message(sourceLineNumbers, Ids.PayloadInMultipleContainers, "The Payload '{0}' can't be added to Container '{1}' because it was already added to Container '{2}'.", payloadId, containerId1, containerId2); | ||
| 17 | } | ||
| 18 | |||
| 19 | public static Message UncompressedPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) | ||
| 20 | { | ||
| 21 | return Message(sourceLineNumbers, Ids.UncompressedPayloadInContainer, "The Payload '{0}' is being added to Container '{1}', overriding its Compressed value of 'no'.", payloadId, containerId); | ||
| 22 | } | ||
| 23 | |||
| 24 | private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) | ||
| 25 | { | ||
| 26 | return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); | ||
| 27 | } | ||
| 28 | |||
| 29 | public enum Ids | ||
| 30 | { | ||
| 31 | LayoutPayloadInContainer = 6900, | ||
| 32 | PayloadInMultipleContainers = 6901, | ||
| 33 | UncompressedPayloadInContainer = 6902, | ||
| 34 | } // last available is 6999. 7000 is LinkerErrors. | ||
| 35 | } | ||
| 36 | } | ||
diff --git a/src/wix/WixToolset.Core/LocalizationParser.cs b/src/wix/WixToolset.Core/LocalizationParser.cs new file mode 100644 index 00000000..d6113fc6 --- /dev/null +++ b/src/wix/WixToolset.Core/LocalizationParser.cs | |||
| @@ -0,0 +1,326 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Xml.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Bind; | ||
| 10 | using WixToolset.Extensibility; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class LocalizationParser : ILocalizationParser | ||
| 14 | { | ||
| 15 | public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl"; | ||
| 16 | private const string XmlElementName = "WixLocalization"; | ||
| 17 | |||
| 18 | internal LocalizationParser(IServiceProvider serviceProvider) | ||
| 19 | { | ||
| 20 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 21 | } | ||
| 22 | |||
| 23 | private IMessaging Messaging { get; } | ||
| 24 | |||
| 25 | public Localization ParseLocalization(string path) | ||
| 26 | { | ||
| 27 | var document = XDocument.Load(path); | ||
| 28 | return this.ParseLocalization(document); | ||
| 29 | } | ||
| 30 | |||
| 31 | public Localization ParseLocalization(XDocument document) | ||
| 32 | { | ||
| 33 | var root = document.Root; | ||
| 34 | Localization localization = null; | ||
| 35 | |||
| 36 | var sourceLineNumbers = SourceLineNumber.CreateFromXObject(root); | ||
| 37 | if (LocalizationParser.XmlElementName == root.Name.LocalName) | ||
| 38 | { | ||
| 39 | if (LocalizationParser.WxlNamespace == root.Name.Namespace) | ||
| 40 | { | ||
| 41 | localization = ParseWixLocalizationElement(this.Messaging, root); | ||
| 42 | } | ||
| 43 | else // invalid or missing namespace | ||
| 44 | { | ||
| 45 | if (null == root.Name.Namespace) | ||
| 46 | { | ||
| 47 | this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, LocalizationParser.WxlNamespace.NamespaceName)); | ||
| 48 | } | ||
| 49 | else | ||
| 50 | { | ||
| 51 | this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, root.Name.LocalName, LocalizationParser.WxlNamespace.NamespaceName)); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
| 55 | else | ||
| 56 | { | ||
| 57 | this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, root.Name.LocalName, "localization", LocalizationParser.XmlElementName)); | ||
| 58 | } | ||
| 59 | |||
| 60 | return localization; | ||
| 61 | } | ||
| 62 | |||
| 63 | /// <summary> | ||
| 64 | /// Adds a WixVariableRow to a dictionary while performing the expected override checks. | ||
| 65 | /// </summary> | ||
| 66 | /// <param name="messaging"></param> | ||
| 67 | /// <param name="variables">Dictionary of variable rows.</param> | ||
| 68 | /// <param name="wixVariableRow">Row to add to the variables dictionary.</param> | ||
| 69 | private static void AddWixVariable(IMessaging messaging, IDictionary<string, BindVariable> variables, BindVariable wixVariableRow) | ||
| 70 | { | ||
| 71 | if (!variables.TryGetValue(wixVariableRow.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) | ||
| 72 | { | ||
| 73 | variables[wixVariableRow.Id] = wixVariableRow; | ||
| 74 | } | ||
| 75 | else if (!wixVariableRow.Overridable) | ||
| 76 | { | ||
| 77 | messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(wixVariableRow.SourceLineNumbers, wixVariableRow.Id)); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | /// <summary> | ||
| 82 | /// Parses the WixLocalization element. | ||
| 83 | /// </summary> | ||
| 84 | /// <param name="messaging"></param> | ||
| 85 | /// <param name="node">Element to parse.</param> | ||
| 86 | private static Localization ParseWixLocalizationElement(IMessaging messaging, XElement node) | ||
| 87 | { | ||
| 88 | var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); | ||
| 89 | int? codepage = null; | ||
| 90 | int? summaryInformationCodepage = null; | ||
| 91 | string culture = null; | ||
| 92 | |||
| 93 | foreach (var attrib in node.Attributes()) | ||
| 94 | { | ||
| 95 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) | ||
| 96 | { | ||
| 97 | switch (attrib.Name.LocalName) | ||
| 98 | { | ||
| 99 | case "Codepage": | ||
| 100 | codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); | ||
| 101 | break; | ||
| 102 | case "SummaryInformationCodepage": | ||
| 103 | summaryInformationCodepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); | ||
| 104 | break; | ||
| 105 | case "Culture": | ||
| 106 | culture = attrib.Value; | ||
| 107 | break; | ||
| 108 | case "Language": | ||
| 109 | // do nothing; @Language is used for locutil which can't convert Culture to lcid | ||
| 110 | break; | ||
| 111 | default: | ||
| 112 | Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); | ||
| 113 | break; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | else | ||
| 117 | { | ||
| 118 | Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | var variables = new Dictionary<string, BindVariable>(); | ||
| 123 | var localizedControls = new Dictionary<string, LocalizedControl>(); | ||
| 124 | |||
| 125 | foreach (var child in node.Elements()) | ||
| 126 | { | ||
| 127 | if (LocalizationParser.WxlNamespace == child.Name.Namespace) | ||
| 128 | { | ||
| 129 | switch (child.Name.LocalName) | ||
| 130 | { | ||
| 131 | case "String": | ||
| 132 | LocalizationParser.ParseString(messaging, child, variables); | ||
| 133 | break; | ||
| 134 | |||
| 135 | case "UI": | ||
| 136 | LocalizationParser.ParseUI(messaging, child, localizedControls); | ||
| 137 | break; | ||
| 138 | |||
| 139 | default: | ||
| 140 | messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); | ||
| 141 | break; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | else | ||
| 145 | { | ||
| 146 | messaging.Write(ErrorMessages.UnsupportedExtensionElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | return messaging.EncounteredError ? null : new Localization(codepage, summaryInformationCodepage, culture, variables, localizedControls); | ||
| 151 | } | ||
| 152 | |||
| 153 | /// <summary> | ||
| 154 | /// Parse a localization string into a WixVariableRow. | ||
| 155 | /// </summary> | ||
| 156 | /// <param name="messaging"></param> | ||
| 157 | /// <param name="node">Element to parse.</param> | ||
| 158 | /// <param name="variables"></param> | ||
| 159 | private static void ParseString(IMessaging messaging, XElement node, IDictionary<string, BindVariable> variables) | ||
| 160 | { | ||
| 161 | string id = null; | ||
| 162 | var overridable = false; | ||
| 163 | var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); | ||
| 164 | |||
| 165 | foreach (var attrib in node.Attributes()) | ||
| 166 | { | ||
| 167 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) | ||
| 168 | { | ||
| 169 | switch (attrib.Name.LocalName) | ||
| 170 | { | ||
| 171 | case "Id": | ||
| 172 | id = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); | ||
| 173 | break; | ||
| 174 | case "Overridable": | ||
| 175 | overridable = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); | ||
| 176 | break; | ||
| 177 | case "Localizable": | ||
| 178 | ; // do nothing | ||
| 179 | break; | ||
| 180 | default: | ||
| 181 | messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); | ||
| 182 | break; | ||
| 183 | } | ||
| 184 | } | ||
| 185 | else | ||
| 186 | { | ||
| 187 | messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | var value = Common.GetInnerText(node); | ||
| 192 | |||
| 193 | if (null == id) | ||
| 194 | { | ||
| 195 | messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "String", "Id")); | ||
| 196 | } | ||
| 197 | else if (0 == id.Length) | ||
| 198 | { | ||
| 199 | messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, "String", "Id", 0)); | ||
| 200 | } | ||
| 201 | |||
| 202 | if (!messaging.EncounteredError) | ||
| 203 | { | ||
| 204 | var variable = new BindVariable | ||
| 205 | { | ||
| 206 | SourceLineNumbers = sourceLineNumbers, | ||
| 207 | Id = id, | ||
| 208 | Overridable = overridable, | ||
| 209 | Value = value, | ||
| 210 | }; | ||
| 211 | |||
| 212 | LocalizationParser.AddWixVariable(messaging, variables, variable); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | /// <summary> | ||
| 217 | /// Parse a localized control. | ||
| 218 | /// </summary> | ||
| 219 | /// <param name="messaging"></param> | ||
| 220 | /// <param name="node">Element to parse.</param> | ||
| 221 | /// <param name="localizedControls">Dictionary of localized controls.</param> | ||
| 222 | private static void ParseUI(IMessaging messaging, XElement node, IDictionary<string, LocalizedControl> localizedControls) | ||
| 223 | { | ||
| 224 | string dialog = null; | ||
| 225 | string control = null; | ||
| 226 | var x = CompilerConstants.IntegerNotSet; | ||
| 227 | var y = CompilerConstants.IntegerNotSet; | ||
| 228 | var width = CompilerConstants.IntegerNotSet; | ||
| 229 | var height = CompilerConstants.IntegerNotSet; | ||
| 230 | var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); | ||
| 231 | var rightToLeft = false; | ||
| 232 | var rightAligned = false; | ||
| 233 | var leftScroll = false; | ||
| 234 | |||
| 235 | foreach (var attrib in node.Attributes()) | ||
| 236 | { | ||
| 237 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) | ||
| 238 | { | ||
| 239 | switch (attrib.Name.LocalName) | ||
| 240 | { | ||
| 241 | case "Dialog": | ||
| 242 | dialog = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); | ||
| 243 | break; | ||
| 244 | case "Control": | ||
| 245 | control = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); | ||
| 246 | break; | ||
| 247 | case "X": | ||
| 248 | x = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 249 | break; | ||
| 250 | case "Y": | ||
| 251 | y = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 252 | break; | ||
| 253 | case "Width": | ||
| 254 | width = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 255 | break; | ||
| 256 | case "Height": | ||
| 257 | height = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); | ||
| 258 | break; | ||
| 259 | case "RightToLeft": | ||
| 260 | rightToLeft = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); | ||
| 261 | break; | ||
| 262 | case "RightAligned": | ||
| 263 | rightAligned = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); | ||
| 264 | break; | ||
| 265 | case "LeftScroll": | ||
| 266 | leftScroll = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); | ||
| 267 | break; | ||
| 268 | default: | ||
| 269 | Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); | ||
| 270 | break; | ||
| 271 | } | ||
| 272 | } | ||
| 273 | else | ||
| 274 | { | ||
| 275 | Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | var text = Common.GetInnerText(node); | ||
| 280 | |||
| 281 | if (String.IsNullOrEmpty(control) && (rightToLeft || rightAligned || leftScroll)) | ||
| 282 | { | ||
| 283 | if (rightToLeft) | ||
| 284 | { | ||
| 285 | messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightToLeft", "Control")); | ||
| 286 | } | ||
| 287 | |||
| 288 | if (rightAligned) | ||
| 289 | { | ||
| 290 | messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightAligned", "Control")); | ||
| 291 | } | ||
| 292 | |||
| 293 | if (leftScroll) | ||
| 294 | { | ||
| 295 | messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "LeftScroll", "Control")); | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | if (String.IsNullOrEmpty(control) && String.IsNullOrEmpty(dialog)) | ||
| 300 | { | ||
| 301 | messaging.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.ToString(), "Dialog", "Control")); | ||
| 302 | } | ||
| 303 | |||
| 304 | if (!messaging.EncounteredError) | ||
| 305 | { | ||
| 306 | var localizedControl = new LocalizedControl(dialog, control, x, y, width, height, rightToLeft, rightAligned, leftScroll, text); | ||
| 307 | var key = localizedControl.GetKey(); | ||
| 308 | if (localizedControls.ContainsKey(key)) | ||
| 309 | { | ||
| 310 | if (String.IsNullOrEmpty(localizedControl.Control)) | ||
| 311 | { | ||
| 312 | messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog)); | ||
| 313 | } | ||
| 314 | else | ||
| 315 | { | ||
| 316 | messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog, localizedControl.Control)); | ||
| 317 | } | ||
| 318 | } | ||
| 319 | else | ||
| 320 | { | ||
| 321 | localizedControls.Add(key, localizedControl); | ||
| 322 | } | ||
| 323 | } | ||
| 324 | } | ||
| 325 | } | ||
| 326 | } | ||
diff --git a/src/wix/WixToolset.Core/ParsedWixVariable.cs b/src/wix/WixToolset.Core/ParsedWixVariable.cs new file mode 100644 index 00000000..9d308b77 --- /dev/null +++ b/src/wix/WixToolset.Core/ParsedWixVariable.cs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | internal class ParsedWixVariable | ||
| 6 | { | ||
| 7 | public int Index { get; set; } | ||
| 8 | |||
| 9 | public int Length { get; set; } | ||
| 10 | |||
| 11 | public string Namespace { get; set; } | ||
| 12 | |||
| 13 | public string Name { get; set; } | ||
| 14 | |||
| 15 | public string Scope { get; set; } | ||
| 16 | |||
| 17 | public string DefaultValue { get; set; } | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/src/wix/WixToolset.Core/Preprocess/IfContext.cs b/src/wix/WixToolset.Core/Preprocess/IfContext.cs new file mode 100644 index 00000000..91173c29 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IfContext.cs | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Preprocess | ||
| 4 | { | ||
| 5 | /// <summary> | ||
| 6 | /// Context for an if statement in the preprocessor. | ||
| 7 | /// </summary> | ||
| 8 | internal class IfContext | ||
| 9 | { | ||
| 10 | private bool keep; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Creates a default if context object, which are used for if's within an inactive preprocessor block | ||
| 14 | /// </summary> | ||
| 15 | public IfContext() | ||
| 16 | { | ||
| 17 | this.WasEverTrue = true; | ||
| 18 | this.IfState = IfState.If; | ||
| 19 | } | ||
| 20 | |||
| 21 | /// <summary> | ||
| 22 | /// Creates an if context object. | ||
| 23 | /// </summary> | ||
| 24 | /// <param name="active">Flag if context is currently active.</param> | ||
| 25 | /// <param name="keep">Flag if context is currently true.</param> | ||
| 26 | /// <param name="state">State of context to start in.</param> | ||
| 27 | public IfContext(bool active, bool keep, IfState state) | ||
| 28 | { | ||
| 29 | this.Active = active; | ||
| 30 | this.keep = keep; | ||
| 31 | this.WasEverTrue = keep; | ||
| 32 | this.IfState = IfState.If; | ||
| 33 | } | ||
| 34 | |||
| 35 | /// <summary> | ||
| 36 | /// Gets and sets if this if context is currently active. | ||
| 37 | /// </summary> | ||
| 38 | /// <value>true if context is active.</value> | ||
| 39 | public bool Active { get; set; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets and sets if context is current true. | ||
| 43 | /// </summary> | ||
| 44 | /// <value>true if context is currently true.</value> | ||
| 45 | public bool IsTrue | ||
| 46 | { | ||
| 47 | get | ||
| 48 | { | ||
| 49 | return this.keep; | ||
| 50 | } | ||
| 51 | |||
| 52 | set | ||
| 53 | { | ||
| 54 | this.keep = value; | ||
| 55 | if (this.keep) | ||
| 56 | { | ||
| 57 | this.WasEverTrue = true; | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | /// <summary> | ||
| 63 | /// Gets if the context was ever true. | ||
| 64 | /// </summary> | ||
| 65 | /// <value>True if context was ever true.</value> | ||
| 66 | public bool WasEverTrue { get; private set; } | ||
| 67 | |||
| 68 | /// <summary> | ||
| 69 | /// Gets the current state of the if context. | ||
| 70 | /// </summary> | ||
| 71 | /// <value>Current state of context.</value> | ||
| 72 | public IfState IfState { get; set; } | ||
| 73 | } | ||
| 74 | } | ||
diff --git a/src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs new file mode 100644 index 00000000..6b56638a --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Preprocess | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Data; | ||
| 7 | |||
| 8 | internal delegate void IfDefEventHandler(object sender, IfDefEventArgs e); | ||
| 9 | |||
| 10 | internal class IfDefEventArgs : EventArgs | ||
| 11 | { | ||
| 12 | public IfDefEventArgs(SourceLineNumber sourceLineNumbers, bool isIfDef, bool isDefined, string variableName) | ||
| 13 | { | ||
| 14 | this.SourceLineNumbers = sourceLineNumbers; | ||
| 15 | this.IsIfDef = isIfDef; | ||
| 16 | this.IsDefined = isDefined; | ||
| 17 | this.VariableName = variableName; | ||
| 18 | } | ||
| 19 | |||
| 20 | public SourceLineNumber SourceLineNumbers { get; } | ||
| 21 | |||
| 22 | public bool IsDefined { get; } | ||
| 23 | |||
| 24 | public bool IsIfDef { get; } | ||
| 25 | |||
| 26 | public string VariableName { get; } | ||
| 27 | } | ||
| 28 | } | ||
diff --git a/src/wix/WixToolset.Core/Preprocess/IfState.cs b/src/wix/WixToolset.Core/Preprocess/IfState.cs new file mode 100644 index 00000000..f5bb3e87 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IfState.cs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Preprocess | ||
| 4 | { | ||
| 5 | /// <summary> | ||
| 6 | /// Current state of the if context. | ||
| 7 | /// </summary> | ||
| 8 | internal enum IfState | ||
| 9 | { | ||
| 10 | /// <summary>Context currently in unknown state.</summary> | ||
| 11 | Unknown, | ||
| 12 | |||
| 13 | /// <summary>Context currently inside if statement.</summary> | ||
| 14 | If, | ||
| 15 | |||
| 16 | /// <summary>Context currently inside elseif statement..</summary> | ||
| 17 | ElseIf, | ||
| 18 | |||
| 19 | /// <summary>Conext currently inside else statement.</summary> | ||
| 20 | Else, | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs new file mode 100644 index 00000000..3c8ff2e8 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Preprocess | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Data; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Included file event handler delegate. | ||
| 10 | /// </summary> | ||
| 11 | /// <param name="sender">Sender of the message.</param> | ||
| 12 | /// <param name="e">Arguments for the included file event.</param> | ||
| 13 | internal delegate void IncludedFileEventHandler(object sender, IncludedFileEventArgs e); | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Event args for included file event. | ||
| 17 | /// </summary> | ||
| 18 | internal class IncludedFileEventArgs : EventArgs | ||
| 19 | { | ||
| 20 | /// <summary> | ||
| 21 | /// Creates a new IncludedFileEventArgs. | ||
| 22 | /// </summary> | ||
| 23 | /// <param name="sourceLineNumbers">Source line numbers for the included file.</param> | ||
| 24 | /// <param name="fullName">The full path of the included file.</param> | ||
| 25 | public IncludedFileEventArgs(SourceLineNumber sourceLineNumbers, string fullName) | ||
| 26 | { | ||
| 27 | this.SourceLineNumbers = sourceLineNumbers; | ||
| 28 | this.FullName = fullName; | ||
| 29 | } | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Gets the full path of the included file. | ||
| 33 | /// </summary> | ||
| 34 | /// <value>The full path of the included file.</value> | ||
| 35 | public string FullName { get; } | ||
| 36 | |||
| 37 | /// <summary> | ||
| 38 | /// Gets the source line numbers. | ||
| 39 | /// </summary> | ||
| 40 | /// <value>The source line numbers.</value> | ||
| 41 | public SourceLineNumber SourceLineNumbers { get; } | ||
| 42 | } | ||
| 43 | } | ||
diff --git a/src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs b/src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs new file mode 100644 index 00000000..086a0f1a --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Preprocess | ||
| 4 | { | ||
| 5 | /// <summary> | ||
| 6 | /// Enumeration for preprocessor operations in if statements. | ||
| 7 | /// </summary> | ||
| 8 | internal enum PreprocessorOperation | ||
| 9 | { | ||
| 10 | /// <summary>The and operator.</summary> | ||
| 11 | And, | ||
| 12 | |||
| 13 | /// <summary>The or operator.</summary> | ||
| 14 | Or, | ||
| 15 | |||
| 16 | /// <summary>The not operator.</summary> | ||
| 17 | Not | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs new file mode 100644 index 00000000..672b4b9f --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Preprocess | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml.Linq; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Preprocessed output stream event handler delegate. | ||
| 10 | /// </summary> | ||
| 11 | /// <param name="sender">Sender of the message.</param> | ||
| 12 | /// <param name="e">Arguments for the preprocessed stream event.</param> | ||
| 13 | internal delegate void ProcessedStreamEventHandler(object sender, ProcessedStreamEventArgs e); | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Event args for preprocessed stream event. | ||
| 17 | /// </summary> | ||
| 18 | internal class ProcessedStreamEventArgs : EventArgs | ||
| 19 | { | ||
| 20 | /// <summary> | ||
| 21 | /// Creates a new ProcessedStreamEventArgs. | ||
| 22 | /// </summary> | ||
| 23 | /// <param name="sourceFile">Source file that is preprocessed.</param> | ||
| 24 | /// <param name="document">Preprocessed output document.</param> | ||
| 25 | public ProcessedStreamEventArgs(string sourceFile, XDocument document) | ||
| 26 | { | ||
| 27 | this.SourceFile = sourceFile; | ||
| 28 | this.Document = document; | ||
| 29 | } | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Gets the full path of the source file. | ||
| 33 | /// </summary> | ||
| 34 | /// <value>The full path of the source file.</value> | ||
| 35 | public string SourceFile { get; } | ||
| 36 | |||
| 37 | /// <summary> | ||
| 38 | /// Gets the preprocessed output stream. | ||
| 39 | /// </summary> | ||
| 40 | /// <value>The the preprocessed output stream.</value> | ||
| 41 | public XDocument Document { get; } | ||
| 42 | } | ||
| 43 | } | ||
diff --git a/src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs new file mode 100644 index 00000000..6d159ad0 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Preprocess | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Data; | ||
| 7 | |||
| 8 | internal delegate void ResolvedVariableEventHandler(object sender, ResolvedVariableEventArgs e); | ||
| 9 | |||
| 10 | internal class ResolvedVariableEventArgs : EventArgs | ||
| 11 | { | ||
| 12 | public ResolvedVariableEventArgs(SourceLineNumber sourceLineNumbers, string variableName, string variableValue) | ||
| 13 | { | ||
| 14 | this.SourceLineNumbers = sourceLineNumbers; | ||
| 15 | this.VariableName = variableName; | ||
| 16 | this.VariableValue = variableValue; | ||
| 17 | } | ||
| 18 | |||
| 19 | public SourceLineNumber SourceLineNumbers { get; } | ||
| 20 | |||
| 21 | public string VariableName { get; } | ||
| 22 | |||
| 23 | public string VariableValue { get; } | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/src/wix/WixToolset.Core/PreprocessContext.cs b/src/wix/WixToolset.Core/PreprocessContext.cs new file mode 100644 index 00000000..986045ff --- /dev/null +++ b/src/wix/WixToolset.Core/PreprocessContext.cs | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Threading; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | |||
| 12 | internal class PreprocessContext : IPreprocessContext | ||
| 13 | { | ||
| 14 | internal PreprocessContext(IServiceProvider serviceProvider) | ||
| 15 | { | ||
| 16 | this.ServiceProvider = serviceProvider; | ||
| 17 | } | ||
| 18 | |||
| 19 | public IServiceProvider ServiceProvider { get; } | ||
| 20 | |||
| 21 | public IReadOnlyCollection<IPreprocessorExtension> Extensions { get; set; } | ||
| 22 | |||
| 23 | public Platform Platform { get; set; } | ||
| 24 | |||
| 25 | public IReadOnlyCollection<string> IncludeSearchPaths { get; set; } | ||
| 26 | |||
| 27 | public string SourcePath { get; set; } | ||
| 28 | |||
| 29 | public IDictionary<string, string> Variables { get; set; } | ||
| 30 | |||
| 31 | public SourceLineNumber CurrentSourceLineNumber { get; set; } | ||
| 32 | |||
| 33 | public CancellationToken CancellationToken { get; set; } | ||
| 34 | } | ||
| 35 | } | ||
diff --git a/src/wix/WixToolset.Core/PreprocessResult.cs b/src/wix/WixToolset.Core/PreprocessResult.cs new file mode 100644 index 00000000..83b29a90 --- /dev/null +++ b/src/wix/WixToolset.Core/PreprocessResult.cs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.Xml.Linq; | ||
| 7 | using WixToolset.Extensibility.Data; | ||
| 8 | |||
| 9 | internal class PreprocessResult : IPreprocessResult | ||
| 10 | { | ||
| 11 | public XDocument Document { get; set; } | ||
| 12 | |||
| 13 | public IReadOnlyCollection<IIncludedFile> IncludedFiles { get; set; } | ||
| 14 | } | ||
| 15 | } | ||
diff --git a/src/wix/WixToolset.Core/Preprocessor.cs b/src/wix/WixToolset.Core/Preprocessor.cs new file mode 100644 index 00000000..603c0e5b --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocessor.cs | |||
| @@ -0,0 +1,1520 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.IO; | ||
| 9 | using System.Text; | ||
| 10 | using System.Text.RegularExpressions; | ||
| 11 | using System.Xml; | ||
| 12 | using System.Xml.Linq; | ||
| 13 | using WixToolset.Core.Preprocess; | ||
| 14 | using WixToolset.Data; | ||
| 15 | using WixToolset.Extensibility; | ||
| 16 | using WixToolset.Extensibility.Data; | ||
| 17 | using WixToolset.Extensibility.Services; | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Preprocessor object | ||
| 21 | /// </summary> | ||
| 22 | internal class Preprocessor : IPreprocessor | ||
| 23 | { | ||
| 24 | private static readonly Regex DefineRegex = new Regex(@"^\s*(?<varName>.+?)\s*(=\s*(?<varValue>.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); | ||
| 25 | private static readonly Regex PragmaRegex = new Regex(@"^\s*(?<pragmaName>.+?)(?<pragmaValue>[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); | ||
| 26 | |||
| 27 | private static readonly XmlReaderSettings DocumentXmlReaderSettings = new XmlReaderSettings() | ||
| 28 | { | ||
| 29 | ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, | ||
| 30 | XmlResolver = null, | ||
| 31 | }; | ||
| 32 | |||
| 33 | private static readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings() | ||
| 34 | { | ||
| 35 | ConformanceLevel = ConformanceLevel.Fragment, | ||
| 36 | ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, | ||
| 37 | XmlResolver = null, | ||
| 38 | }; | ||
| 39 | |||
| 40 | internal Preprocessor(IServiceProvider serviceProvider) | ||
| 41 | { | ||
| 42 | this.ServiceProvider = serviceProvider; | ||
| 43 | |||
| 44 | this.Messaging = this.ServiceProvider.GetService<IMessaging>(); | ||
| 45 | } | ||
| 46 | |||
| 47 | private IServiceProvider ServiceProvider { get; } | ||
| 48 | |||
| 49 | private IMessaging Messaging { get; } | ||
| 50 | |||
| 51 | /// <summary> | ||
| 52 | /// Event for ifdef/ifndef directives. | ||
| 53 | /// </summary> | ||
| 54 | public event IfDefEventHandler IfDef; | ||
| 55 | |||
| 56 | /// <summary> | ||
| 57 | /// Event for included files. | ||
| 58 | /// </summary> | ||
| 59 | public event IncludedFileEventHandler IncludedFile; | ||
| 60 | |||
| 61 | /// <summary> | ||
| 62 | /// Event for preprocessed stream. | ||
| 63 | /// </summary> | ||
| 64 | public event ProcessedStreamEventHandler ProcessedStream; | ||
| 65 | |||
| 66 | // <summary> | ||
| 67 | // Event for resolved variables. | ||
| 68 | // </summary> | ||
| 69 | // TOOD: Remove? | ||
| 70 | //public event ResolvedVariableEventHandler ResolvedVariable; | ||
| 71 | |||
| 72 | /// <summary> | ||
| 73 | /// Get the source line information for the current element. The precompiler will insert | ||
| 74 | /// special source line number information for each element that it encounters. | ||
| 75 | /// </summary> | ||
| 76 | /// <param name="node">Element to get source line information for.</param> | ||
| 77 | /// <returns> | ||
| 78 | /// The source line number used to author the element being processed or | ||
| 79 | /// null if the preprocessor did not process the element or the node is | ||
| 80 | /// not an element. | ||
| 81 | /// </returns> | ||
| 82 | public static SourceLineNumber GetSourceLineNumbers(XObject node) | ||
| 83 | { | ||
| 84 | return SourceLineNumber.GetFromXAnnotation(node); | ||
| 85 | } | ||
| 86 | |||
| 87 | /// <summary> | ||
| 88 | /// Preprocesses a file. | ||
| 89 | /// </summary> | ||
| 90 | /// <param name="context">The preprocessing context.</param> | ||
| 91 | /// <returns>XDocument with the postprocessed data.</returns> | ||
| 92 | public IPreprocessResult Preprocess(IPreprocessContext context) | ||
| 93 | { | ||
| 94 | var state = new ProcessingState(this.ServiceProvider, context); | ||
| 95 | |||
| 96 | this.PreProcess(state); | ||
| 97 | |||
| 98 | IPreprocessResult result; | ||
| 99 | using (var reader = XmlReader.Create(state.Context.SourcePath, DocumentXmlReaderSettings)) | ||
| 100 | { | ||
| 101 | result = this.Process(state, reader); | ||
| 102 | } | ||
| 103 | |||
| 104 | this.PostProcess(state, result); | ||
| 105 | |||
| 106 | return result; | ||
| 107 | } | ||
| 108 | |||
| 109 | /// <summary> | ||
| 110 | /// Preprocesses a file. | ||
| 111 | /// </summary> | ||
| 112 | /// <param name="context">The preprocessing context.</param> | ||
| 113 | /// <param name="reader">XmlReader to processing the context.</param> | ||
| 114 | /// <returns>XDocument with the postprocessed data.</returns> | ||
| 115 | public IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader) | ||
| 116 | { | ||
| 117 | if (String.IsNullOrEmpty(context.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI)) | ||
| 118 | { | ||
| 119 | var uri = new Uri(reader.BaseURI); | ||
| 120 | context.SourcePath = uri.AbsolutePath; | ||
| 121 | } | ||
| 122 | |||
| 123 | var state = new ProcessingState(this.ServiceProvider, context); | ||
| 124 | |||
| 125 | this.PreProcess(state); | ||
| 126 | |||
| 127 | var result = this.Process(state, reader); | ||
| 128 | |||
| 129 | this.PostProcess(state, result); | ||
| 130 | |||
| 131 | return result; | ||
| 132 | } | ||
| 133 | |||
| 134 | /// <summary> | ||
| 135 | /// Preprocesses a file. | ||
| 136 | /// </summary> | ||
| 137 | /// <param name="state">The preprocessing context.</param> | ||
| 138 | /// <param name="reader">XmlReader to processing the context.</param> | ||
| 139 | /// <returns>XDocument with the postprocessed data.</returns> | ||
| 140 | private IPreprocessResult Process(ProcessingState state, XmlReader reader) | ||
| 141 | { | ||
| 142 | state.CurrentFileStack.Push(state.Helper.GetVariableValue(state.Context, "sys", "SOURCEFILEDIR")); | ||
| 143 | |||
| 144 | // Process the reader into the output. | ||
| 145 | IPreprocessResult result = null; | ||
| 146 | try | ||
| 147 | { | ||
| 148 | this.PreprocessReader(state, false, reader, state.Output, 0); | ||
| 149 | |||
| 150 | // Fire event with post-processed document. | ||
| 151 | this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(state.Context.SourcePath, state.Output)); | ||
| 152 | |||
| 153 | if (!this.Messaging.EncounteredError) | ||
| 154 | { | ||
| 155 | result = this.ServiceProvider.GetService<IPreprocessResult>(); | ||
| 156 | result.Document = state.Output; | ||
| 157 | result.IncludedFiles = state.IncludedFiles; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | catch (XmlException e) | ||
| 161 | { | ||
| 162 | this.UpdateCurrentLineNumber(state, reader, 0); | ||
| 163 | throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); | ||
| 164 | } | ||
| 165 | |||
| 166 | return result; | ||
| 167 | } | ||
| 168 | |||
| 169 | /// <summary> | ||
| 170 | /// Determins if string is an operator. | ||
| 171 | /// </summary> | ||
| 172 | /// <param name="operation">String to check.</param> | ||
| 173 | /// <returns>true if string is an operator.</returns> | ||
| 174 | private static bool IsOperator(string operation) | ||
| 175 | { | ||
| 176 | if (operation == null) | ||
| 177 | { | ||
| 178 | return false; | ||
| 179 | } | ||
| 180 | |||
| 181 | operation = operation.Trim(); | ||
| 182 | if (0 == operation.Length) | ||
| 183 | { | ||
| 184 | return false; | ||
| 185 | } | ||
| 186 | |||
| 187 | if ("=" == operation || | ||
| 188 | "!=" == operation || | ||
| 189 | "<" == operation || | ||
| 190 | "<=" == operation || | ||
| 191 | ">" == operation || | ||
| 192 | ">=" == operation || | ||
| 193 | "~=" == operation) | ||
| 194 | { | ||
| 195 | return true; | ||
| 196 | } | ||
| 197 | return false; | ||
| 198 | } | ||
| 199 | |||
| 200 | /// <summary> | ||
| 201 | /// Determines if expression is currently inside quotes. | ||
| 202 | /// </summary> | ||
| 203 | /// <param name="expression">Expression to evaluate.</param> | ||
| 204 | /// <param name="index">Index to start searching in expression.</param> | ||
| 205 | /// <returns>true if expression is inside in quotes.</returns> | ||
| 206 | private static bool InsideQuotes(string expression, int index) | ||
| 207 | { | ||
| 208 | if (index == -1) | ||
| 209 | { | ||
| 210 | return false; | ||
| 211 | } | ||
| 212 | |||
| 213 | var numQuotes = 0; | ||
| 214 | var tmpIndex = 0; | ||
| 215 | while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex))) | ||
| 216 | { | ||
| 217 | numQuotes++; | ||
| 218 | tmpIndex++; | ||
| 219 | } | ||
| 220 | |||
| 221 | // found an even number of quotes before the index, so we're not inside | ||
| 222 | if (numQuotes % 2 == 0) | ||
| 223 | { | ||
| 224 | return false; | ||
| 225 | } | ||
| 226 | |||
| 227 | // found an odd number of quotes, so we are inside | ||
| 228 | return true; | ||
| 229 | } | ||
| 230 | |||
| 231 | /// <summary> | ||
| 232 | /// Tests expression to see if it starts with a keyword. | ||
| 233 | /// </summary> | ||
| 234 | /// <param name="expression">Expression to test.</param> | ||
| 235 | /// <param name="operation">Operation to test for.</param> | ||
| 236 | /// <returns>true if expression starts with a keyword.</returns> | ||
| 237 | private static bool StartsWithKeyword(string expression, PreprocessorOperation operation) | ||
| 238 | { | ||
| 239 | expression = expression.ToUpperInvariant(); | ||
| 240 | switch (operation) | ||
| 241 | { | ||
| 242 | case PreprocessorOperation.Not: | ||
| 243 | if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal)) | ||
| 244 | { | ||
| 245 | return true; | ||
| 246 | } | ||
| 247 | break; | ||
| 248 | case PreprocessorOperation.And: | ||
| 249 | if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal)) | ||
| 250 | { | ||
| 251 | return true; | ||
| 252 | } | ||
| 253 | break; | ||
| 254 | case PreprocessorOperation.Or: | ||
| 255 | if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal)) | ||
| 256 | { | ||
| 257 | return true; | ||
| 258 | } | ||
| 259 | break; | ||
| 260 | default: | ||
| 261 | break; | ||
| 262 | } | ||
| 263 | return false; | ||
| 264 | } | ||
| 265 | |||
| 266 | /// <summary> | ||
| 267 | /// Processes an xml reader into an xml writer. | ||
| 268 | /// </summary> | ||
| 269 | /// <param name="state"></param> | ||
| 270 | /// <param name="include">Specifies if reader is from an included file.</param> | ||
| 271 | /// <param name="reader">Reader for the source document.</param> | ||
| 272 | /// <param name="container">Node where content should be added.</param> | ||
| 273 | /// <param name="offset">Original offset for the line numbers being processed.</param> | ||
| 274 | private void PreprocessReader(ProcessingState state, bool include, XmlReader reader, XContainer container, int offset) | ||
| 275 | { | ||
| 276 | var currentContainer = container; | ||
| 277 | var containerStack = new Stack<XContainer>(); | ||
| 278 | |||
| 279 | var ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code | ||
| 280 | var ifStack = new Stack<IfContext>(); | ||
| 281 | |||
| 282 | // process the reader into the writer | ||
| 283 | while (reader.Read()) | ||
| 284 | { | ||
| 285 | // update information here in case an error occurs before the next read | ||
| 286 | this.UpdateCurrentLineNumber(state, reader, offset); | ||
| 287 | |||
| 288 | var sourceLineNumbers = state.Context.CurrentSourceLineNumber; | ||
| 289 | |||
| 290 | // check for changes in conditional processing | ||
| 291 | if (XmlNodeType.ProcessingInstruction == reader.NodeType) | ||
| 292 | { | ||
| 293 | var ignore = false; | ||
| 294 | string name = null; | ||
| 295 | |||
| 296 | switch (reader.LocalName) | ||
| 297 | { | ||
| 298 | case "if": | ||
| 299 | ifStack.Push(ifContext); | ||
| 300 | if (ifContext.IsTrue) | ||
| 301 | { | ||
| 302 | ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(state, reader.Value), IfState.If); | ||
| 303 | } | ||
| 304 | else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true | ||
| 305 | { | ||
| 306 | ifContext = new IfContext(); | ||
| 307 | } | ||
| 308 | ignore = true; | ||
| 309 | break; | ||
| 310 | |||
| 311 | case "ifdef": | ||
| 312 | ifStack.Push(ifContext); | ||
| 313 | name = reader.Value.Trim(); | ||
| 314 | if (ifContext.IsTrue) | ||
| 315 | { | ||
| 316 | ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); | ||
| 317 | } | ||
| 318 | else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true | ||
| 319 | { | ||
| 320 | ifContext = new IfContext(); | ||
| 321 | } | ||
| 322 | ignore = true; | ||
| 323 | this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); | ||
| 324 | break; | ||
| 325 | |||
| 326 | case "ifndef": | ||
| 327 | ifStack.Push(ifContext); | ||
| 328 | name = reader.Value.Trim(); | ||
| 329 | if (ifContext.IsTrue) | ||
| 330 | { | ||
| 331 | ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); | ||
| 332 | } | ||
| 333 | else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true | ||
| 334 | { | ||
| 335 | ifContext = new IfContext(); | ||
| 336 | } | ||
| 337 | ignore = true; | ||
| 338 | this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); | ||
| 339 | break; | ||
| 340 | |||
| 341 | case "elseif": | ||
| 342 | if (0 == ifStack.Count) | ||
| 343 | { | ||
| 344 | throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); | ||
| 345 | } | ||
| 346 | |||
| 347 | if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) | ||
| 348 | { | ||
| 349 | throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); | ||
| 350 | } | ||
| 351 | |||
| 352 | ifContext.IfState = IfState.ElseIf; // we're now in an elseif | ||
| 353 | if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test | ||
| 354 | { | ||
| 355 | ifContext.IsTrue = this.EvaluateExpression(state, reader.Value); | ||
| 356 | } | ||
| 357 | else if (ifContext.IsTrue) | ||
| 358 | { | ||
| 359 | ifContext.IsTrue = false; | ||
| 360 | } | ||
| 361 | ignore = true; | ||
| 362 | break; | ||
| 363 | |||
| 364 | case "else": | ||
| 365 | if (0 == ifStack.Count) | ||
| 366 | { | ||
| 367 | throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); | ||
| 368 | } | ||
| 369 | |||
| 370 | if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) | ||
| 371 | { | ||
| 372 | throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); | ||
| 373 | } | ||
| 374 | |||
| 375 | ifContext.IfState = IfState.Else; // we're now in an else | ||
| 376 | ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now | ||
| 377 | ignore = true; | ||
| 378 | break; | ||
| 379 | |||
| 380 | case "endif": | ||
| 381 | if (0 == ifStack.Count) | ||
| 382 | { | ||
| 383 | throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif")); | ||
| 384 | } | ||
| 385 | |||
| 386 | ifContext = ifStack.Pop(); | ||
| 387 | ignore = true; | ||
| 388 | break; | ||
| 389 | } | ||
| 390 | |||
| 391 | if (ignore) // ignore this node since we just handled it above | ||
| 392 | { | ||
| 393 | continue; | ||
| 394 | } | ||
| 395 | } | ||
| 396 | |||
| 397 | if (!ifContext.Active || !ifContext.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing | ||
| 398 | { | ||
| 399 | continue; | ||
| 400 | } | ||
| 401 | |||
| 402 | switch (reader.NodeType) | ||
| 403 | { | ||
| 404 | case XmlNodeType.XmlDeclaration: | ||
| 405 | var document = currentContainer as XDocument; | ||
| 406 | if (null != document) | ||
| 407 | { | ||
| 408 | document.Declaration = new XDeclaration(null, null, null); | ||
| 409 | while (reader.MoveToNextAttribute()) | ||
| 410 | { | ||
| 411 | switch (reader.LocalName) | ||
| 412 | { | ||
| 413 | case "version": | ||
| 414 | document.Declaration.Version = reader.Value; | ||
| 415 | break; | ||
| 416 | |||
| 417 | case "encoding": | ||
| 418 | document.Declaration.Encoding = reader.Value; | ||
| 419 | break; | ||
| 420 | |||
| 421 | case "standalone": | ||
| 422 | document.Declaration.Standalone = reader.Value; | ||
| 423 | break; | ||
| 424 | } | ||
| 425 | } | ||
| 426 | |||
| 427 | } | ||
| 428 | //else | ||
| 429 | //{ | ||
| 430 | // display an error? Can this happen? | ||
| 431 | //} | ||
| 432 | break; | ||
| 433 | |||
| 434 | case XmlNodeType.ProcessingInstruction: | ||
| 435 | switch (reader.LocalName) | ||
| 436 | { | ||
| 437 | case "define": | ||
| 438 | this.PreprocessDefine(state, reader.Value); | ||
| 439 | break; | ||
| 440 | |||
| 441 | case "error": | ||
| 442 | this.PreprocessError(state, reader.Value); | ||
| 443 | break; | ||
| 444 | |||
| 445 | case "warning": | ||
| 446 | this.PreprocessWarning(state, reader.Value); | ||
| 447 | break; | ||
| 448 | |||
| 449 | case "undef": | ||
| 450 | this.PreprocessUndef(state, reader.Value); | ||
| 451 | break; | ||
| 452 | |||
| 453 | case "include": | ||
| 454 | this.UpdateCurrentLineNumber(state, reader, offset); | ||
| 455 | this.PreprocessInclude(state, reader.Value, currentContainer); | ||
| 456 | break; | ||
| 457 | |||
| 458 | case "foreach": | ||
| 459 | this.PreprocessForeach(state, reader, currentContainer, offset); | ||
| 460 | break; | ||
| 461 | |||
| 462 | case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error | ||
| 463 | throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); | ||
| 464 | |||
| 465 | case "pragma": | ||
| 466 | this.PreprocessPragma(state, reader.Value, currentContainer); | ||
| 467 | break; | ||
| 468 | |||
| 469 | default: | ||
| 470 | // unknown processing instructions are currently ignored | ||
| 471 | break; | ||
| 472 | } | ||
| 473 | break; | ||
| 474 | |||
| 475 | case XmlNodeType.Element: | ||
| 476 | if (0 < state.IncludeNextStack.Count && state.IncludeNextStack.Peek()) | ||
| 477 | { | ||
| 478 | if ("Include" != reader.LocalName) | ||
| 479 | { | ||
| 480 | this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include")); | ||
| 481 | } | ||
| 482 | |||
| 483 | state.IncludeNextStack.Pop(); | ||
| 484 | state.IncludeNextStack.Push(false); | ||
| 485 | break; | ||
| 486 | } | ||
| 487 | |||
| 488 | var empty = reader.IsEmptyElement; | ||
| 489 | var ns = XNamespace.Get(reader.NamespaceURI); | ||
| 490 | var element = new XElement(ns + reader.LocalName); | ||
| 491 | currentContainer.Add(element); | ||
| 492 | |||
| 493 | this.UpdateCurrentLineNumber(state, reader, offset); | ||
| 494 | element.AddAnnotation(sourceLineNumbers); | ||
| 495 | |||
| 496 | while (reader.MoveToNextAttribute()) | ||
| 497 | { | ||
| 498 | var value = state.Helper.PreprocessString(state.Context, reader.Value); | ||
| 499 | |||
| 500 | var attribNamespace = XNamespace.Get(reader.NamespaceURI); | ||
| 501 | attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; | ||
| 502 | |||
| 503 | element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); | ||
| 504 | } | ||
| 505 | |||
| 506 | if (!empty) | ||
| 507 | { | ||
| 508 | containerStack.Push(currentContainer); | ||
| 509 | currentContainer = element; | ||
| 510 | } | ||
| 511 | break; | ||
| 512 | |||
| 513 | case XmlNodeType.EndElement: | ||
| 514 | if (0 < reader.Depth || !include) | ||
| 515 | { | ||
| 516 | currentContainer = containerStack.Pop(); | ||
| 517 | } | ||
| 518 | break; | ||
| 519 | |||
| 520 | case XmlNodeType.Text: | ||
| 521 | var postprocessedText = state.Helper.PreprocessString(state.Context, reader.Value); | ||
| 522 | currentContainer.Add(postprocessedText); | ||
| 523 | break; | ||
| 524 | |||
| 525 | case XmlNodeType.CDATA: | ||
| 526 | var postprocessedValue = state.Helper.PreprocessString(state.Context, reader.Value); | ||
| 527 | currentContainer.Add(new XCData(postprocessedValue)); | ||
| 528 | break; | ||
| 529 | |||
| 530 | default: | ||
| 531 | break; | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | if (0 != ifStack.Count) | ||
| 536 | { | ||
| 537 | throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "if", "endif")); | ||
| 538 | } | ||
| 539 | |||
| 540 | // TODO: can this actually happen? | ||
| 541 | if (0 != containerStack.Count) | ||
| 542 | { | ||
| 543 | throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "nodes", "nodes")); | ||
| 544 | } | ||
| 545 | } | ||
| 546 | |||
| 547 | /// <summary> | ||
| 548 | /// Processes an error processing instruction. | ||
| 549 | /// </summary> | ||
| 550 | /// <param name="state"></param> | ||
| 551 | /// <param name="errorMessage">Text from source.</param> | ||
| 552 | private void PreprocessError(ProcessingState state, string errorMessage) | ||
| 553 | { | ||
| 554 | // Resolve other variables in the error message. | ||
| 555 | errorMessage = state.Helper.PreprocessString(state.Context, errorMessage); | ||
| 556 | |||
| 557 | throw new WixException(ErrorMessages.PreprocessorError(state.Context.CurrentSourceLineNumber, errorMessage)); | ||
| 558 | } | ||
| 559 | |||
| 560 | /// <summary> | ||
| 561 | /// Processes a warning processing instruction. | ||
| 562 | /// </summary> | ||
| 563 | /// <param name="state"></param> | ||
| 564 | /// <param name="warningMessage">Text from source.</param> | ||
| 565 | private void PreprocessWarning(ProcessingState state, string warningMessage) | ||
| 566 | { | ||
| 567 | // Resolve other variables in the warning message. | ||
| 568 | warningMessage = state.Helper.PreprocessString(state.Context, warningMessage); | ||
| 569 | |||
| 570 | this.Messaging.Write(WarningMessages.PreprocessorWarning(state.Context.CurrentSourceLineNumber, warningMessage)); | ||
| 571 | } | ||
| 572 | |||
| 573 | /// <summary> | ||
| 574 | /// Processes a define processing instruction and creates the appropriate parameter. | ||
| 575 | /// </summary> | ||
| 576 | /// <param name="state"></param> | ||
| 577 | /// <param name="originalDefine">Text from source.</param> | ||
| 578 | private void PreprocessDefine(ProcessingState state, string originalDefine) | ||
| 579 | { | ||
| 580 | var match = DefineRegex.Match(originalDefine); | ||
| 581 | |||
| 582 | if (!match.Success) | ||
| 583 | { | ||
| 584 | throw new WixException(ErrorMessages.IllegalDefineStatement(state.Context.CurrentSourceLineNumber, originalDefine)); | ||
| 585 | } | ||
| 586 | |||
| 587 | var defineName = match.Groups["varName"].Value; | ||
| 588 | var defineValue = match.Groups["varValue"].Value; | ||
| 589 | |||
| 590 | // strip off the optional quotes | ||
| 591 | if (1 < defineValue.Length && | ||
| 592 | ((defineValue.StartsWith("\"", StringComparison.Ordinal) && defineValue.EndsWith("\"", StringComparison.Ordinal)) | ||
| 593 | || (defineValue.StartsWith("'", StringComparison.Ordinal) && defineValue.EndsWith("'", StringComparison.Ordinal)))) | ||
| 594 | { | ||
| 595 | defineValue = defineValue.Substring(1, defineValue.Length - 2); | ||
| 596 | } | ||
| 597 | |||
| 598 | // resolve other variables in the variable value | ||
| 599 | defineValue = state.Helper.PreprocessString(state.Context, defineValue); | ||
| 600 | |||
| 601 | if (defineName.StartsWith("var.", StringComparison.Ordinal)) | ||
| 602 | { | ||
| 603 | state.Helper.AddVariable(state.Context, defineName.Substring(4), defineValue); | ||
| 604 | } | ||
| 605 | else | ||
| 606 | { | ||
| 607 | state.Helper.AddVariable(state.Context, defineName, defineValue); | ||
| 608 | } | ||
| 609 | } | ||
| 610 | |||
| 611 | /// <summary> | ||
| 612 | /// Processes an undef processing instruction and creates the appropriate parameter. | ||
| 613 | /// </summary> | ||
| 614 | /// <param name="state"></param> | ||
| 615 | /// <param name="originalDefine">Text from source.</param> | ||
| 616 | private void PreprocessUndef(ProcessingState state, string originalDefine) | ||
| 617 | { | ||
| 618 | var name = state.Helper.PreprocessString(state.Context, originalDefine.Trim()); | ||
| 619 | |||
| 620 | if (name.StartsWith("var.", StringComparison.Ordinal)) | ||
| 621 | { | ||
| 622 | state.Helper.RemoveVariable(state.Context, name.Substring(4)); | ||
| 623 | } | ||
| 624 | else | ||
| 625 | { | ||
| 626 | state.Helper.RemoveVariable(state.Context, name); | ||
| 627 | } | ||
| 628 | } | ||
| 629 | |||
| 630 | /// <summary> | ||
| 631 | /// Processes an included file. | ||
| 632 | /// </summary> | ||
| 633 | /// <param name="state"></param> | ||
| 634 | /// <param name="includePath">Path to included file.</param> | ||
| 635 | /// <param name="parent">Parent container for included content.</param> | ||
| 636 | private void PreprocessInclude(ProcessingState state, string includePath, XContainer parent) | ||
| 637 | { | ||
| 638 | var sourceLineNumbers = state.Context.CurrentSourceLineNumber; | ||
| 639 | |||
| 640 | // Preprocess variables in the path. | ||
| 641 | includePath = state.Helper.PreprocessString(state.Context, includePath); | ||
| 642 | |||
| 643 | var includeFile = this.GetIncludeFile(state, includePath); | ||
| 644 | |||
| 645 | if (null == includeFile) | ||
| 646 | { | ||
| 647 | throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, includePath, "include")); | ||
| 648 | } | ||
| 649 | |||
| 650 | using (var reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings)) | ||
| 651 | { | ||
| 652 | this.PushInclude(state, includeFile); | ||
| 653 | |||
| 654 | // process the included reader into the writer | ||
| 655 | try | ||
| 656 | { | ||
| 657 | this.PreprocessReader(state, true, reader, parent, 0); | ||
| 658 | } | ||
| 659 | catch (XmlException e) | ||
| 660 | { | ||
| 661 | this.UpdateCurrentLineNumber(state, reader, 0); | ||
| 662 | throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "source", e.Message)); | ||
| 663 | } | ||
| 664 | |||
| 665 | this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile)); | ||
| 666 | |||
| 667 | var includedFile = this.ServiceProvider.GetService<IIncludedFile>(); | ||
| 668 | includedFile.Path = includeFile; | ||
| 669 | includedFile.SourceLineNumbers = sourceLineNumbers; | ||
| 670 | |||
| 671 | state.IncludedFiles.Add(includedFile); | ||
| 672 | |||
| 673 | this.PopInclude(state); | ||
| 674 | } | ||
| 675 | } | ||
| 676 | |||
| 677 | /// <summary> | ||
| 678 | /// Preprocess a foreach processing instruction. | ||
| 679 | /// </summary> | ||
| 680 | /// <param name="state"></param> | ||
| 681 | /// <param name="reader">The xml reader.</param> | ||
| 682 | /// <param name="container">The container where to output processed data.</param> | ||
| 683 | /// <param name="offset">Offset for the line numbers.</param> | ||
| 684 | private void PreprocessForeach(ProcessingState state, XmlReader reader, XContainer container, int offset) | ||
| 685 | { | ||
| 686 | // Find the "in" token. | ||
| 687 | var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); | ||
| 688 | if (0 > indexOfInToken) | ||
| 689 | { | ||
| 690 | throw new WixException(ErrorMessages.IllegalForeach(state.Context.CurrentSourceLineNumber, reader.Value)); | ||
| 691 | } | ||
| 692 | |||
| 693 | // parse out the variable name | ||
| 694 | var varName = reader.Value.Substring(0, indexOfInToken).Trim(); | ||
| 695 | var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); | ||
| 696 | |||
| 697 | // preprocess the variable values string because it might be a variable itself | ||
| 698 | varValuesString = state.Helper.PreprocessString(state.Context, varValuesString); | ||
| 699 | |||
| 700 | var varValues = varValuesString.Split(';'); | ||
| 701 | |||
| 702 | // go through all the empty strings | ||
| 703 | while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType) | ||
| 704 | { | ||
| 705 | } | ||
| 706 | |||
| 707 | // get the offset of this xml fragment (for some reason its always off by 1) | ||
| 708 | var lineInfoReader = reader as IXmlLineInfo; | ||
| 709 | if (null != lineInfoReader) | ||
| 710 | { | ||
| 711 | offset += lineInfoReader.LineNumber - 1; | ||
| 712 | } | ||
| 713 | |||
| 714 | var textReader = reader as XmlTextReader; | ||
| 715 | // dump the xml to a string (maintaining whitespace if possible) | ||
| 716 | if (null != textReader) | ||
| 717 | { | ||
| 718 | textReader.WhitespaceHandling = WhitespaceHandling.All; | ||
| 719 | } | ||
| 720 | |||
| 721 | var fragmentBuilder = new StringBuilder(); | ||
| 722 | var nestedForeachCount = 1; | ||
| 723 | while (nestedForeachCount != 0) | ||
| 724 | { | ||
| 725 | if (reader.NodeType == XmlNodeType.ProcessingInstruction) | ||
| 726 | { | ||
| 727 | switch (reader.LocalName) | ||
| 728 | { | ||
| 729 | case "foreach": | ||
| 730 | ++nestedForeachCount; | ||
| 731 | // Output the foreach statement | ||
| 732 | fragmentBuilder.AppendFormat("<?foreach {0}?>", reader.Value); | ||
| 733 | break; | ||
| 734 | |||
| 735 | case "endforeach": | ||
| 736 | --nestedForeachCount; | ||
| 737 | if (0 != nestedForeachCount) | ||
| 738 | { | ||
| 739 | fragmentBuilder.Append("<?endforeach ?>"); | ||
| 740 | } | ||
| 741 | break; | ||
| 742 | |||
| 743 | default: | ||
| 744 | fragmentBuilder.AppendFormat("<?{0} {1}?>", reader.LocalName, reader.Value); | ||
| 745 | break; | ||
| 746 | } | ||
| 747 | } | ||
| 748 | else if (reader.NodeType == XmlNodeType.Element) | ||
| 749 | { | ||
| 750 | fragmentBuilder.Append(reader.ReadOuterXml()); | ||
| 751 | continue; | ||
| 752 | } | ||
| 753 | else if (reader.NodeType == XmlNodeType.Whitespace) | ||
| 754 | { | ||
| 755 | // Or output the whitespace | ||
| 756 | fragmentBuilder.Append(reader.Value); | ||
| 757 | } | ||
| 758 | else if (reader.NodeType == XmlNodeType.None) | ||
| 759 | { | ||
| 760 | throw new WixException(ErrorMessages.ExpectedEndforeach(state.Context.CurrentSourceLineNumber)); | ||
| 761 | } | ||
| 762 | |||
| 763 | reader.Read(); | ||
| 764 | } | ||
| 765 | |||
| 766 | using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) | ||
| 767 | { | ||
| 768 | // process each iteration, updating the variable's value each time | ||
| 769 | foreach (var varValue in varValues) | ||
| 770 | { | ||
| 771 | using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) | ||
| 772 | { | ||
| 773 | // Always overwrite foreach variables. | ||
| 774 | state.Helper.AddVariable(state.Context, varName, varValue, false); | ||
| 775 | |||
| 776 | try | ||
| 777 | { | ||
| 778 | this.PreprocessReader(state, false, loopReader, container, offset); | ||
| 779 | } | ||
| 780 | catch (XmlException e) | ||
| 781 | { | ||
| 782 | this.UpdateCurrentLineNumber(state, loopReader, offset); | ||
| 783 | throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); | ||
| 784 | } | ||
| 785 | |||
| 786 | fragmentStream.Position = 0; // seek back to the beginning for the next loop. | ||
| 787 | } | ||
| 788 | } | ||
| 789 | } | ||
| 790 | } | ||
| 791 | |||
| 792 | /// <summary> | ||
| 793 | /// Processes a pragma processing instruction | ||
| 794 | /// </summary> | ||
| 795 | /// <param name="state"></param> | ||
| 796 | /// <param name="pragmaText">Text from source.</param> | ||
| 797 | /// <param name="parent"></param> | ||
| 798 | private void PreprocessPragma(ProcessingState state, string pragmaText, XContainer parent) | ||
| 799 | { | ||
| 800 | var match = PragmaRegex.Match(pragmaText); | ||
| 801 | |||
| 802 | if (!match.Success) | ||
| 803 | { | ||
| 804 | throw new WixException(ErrorMessages.InvalidPreprocessorPragma(state.Context.CurrentSourceLineNumber, pragmaText)); | ||
| 805 | } | ||
| 806 | |||
| 807 | // resolve other variables in the pragma argument(s) | ||
| 808 | var pragmaArgs = state.Helper.PreprocessString(state.Context, match.Groups["pragmaValue"].Value).Trim(); | ||
| 809 | |||
| 810 | try | ||
| 811 | { | ||
| 812 | state.Helper.PreprocessPragma(state.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); | ||
| 813 | } | ||
| 814 | catch (Exception e) | ||
| 815 | { | ||
| 816 | throw new WixException(ErrorMessages.PreprocessorExtensionPragmaFailed(state.Context.CurrentSourceLineNumber, pragmaText, e.Message)); | ||
| 817 | } | ||
| 818 | } | ||
| 819 | |||
| 820 | /// <summary> | ||
| 821 | /// Gets the next token in an expression. | ||
| 822 | /// </summary> | ||
| 823 | /// <param name="state"></param> | ||
| 824 | /// <param name="originalExpression">Expression to parse.</param> | ||
| 825 | /// <param name="expression">Expression with token removed.</param> | ||
| 826 | /// <param name="stringLiteral">Flag if token is a string literal instead of a variable.</param> | ||
| 827 | /// <returns>Next token.</returns> | ||
| 828 | private string GetNextToken(ProcessingState state, string originalExpression, ref string expression, out bool stringLiteral) | ||
| 829 | { | ||
| 830 | stringLiteral = false; | ||
| 831 | var token = String.Empty; | ||
| 832 | expression = expression.Trim(); | ||
| 833 | if (0 == expression.Length) | ||
| 834 | { | ||
| 835 | return String.Empty; | ||
| 836 | } | ||
| 837 | |||
| 838 | if (expression.StartsWith("\"", StringComparison.Ordinal)) | ||
| 839 | { | ||
| 840 | stringLiteral = true; | ||
| 841 | var endingQuotes = expression.IndexOf('\"', 1); | ||
| 842 | if (-1 == endingQuotes) | ||
| 843 | { | ||
| 844 | throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 845 | } | ||
| 846 | |||
| 847 | // cut the quotes off the string | ||
| 848 | token = state.Helper.PreprocessString(state.Context, expression.Substring(1, endingQuotes - 1)); | ||
| 849 | |||
| 850 | // advance past this string | ||
| 851 | expression = expression.Substring(endingQuotes + 1).Trim(); | ||
| 852 | } | ||
| 853 | else if (expression.StartsWith("$(", StringComparison.Ordinal)) | ||
| 854 | { | ||
| 855 | // Find the ending paren of the expression | ||
| 856 | var endingParen = -1; | ||
| 857 | var openedCount = 1; | ||
| 858 | for (var i = 2; i < expression.Length; i++) | ||
| 859 | { | ||
| 860 | if ('(' == expression[i]) | ||
| 861 | { | ||
| 862 | openedCount++; | ||
| 863 | } | ||
| 864 | else if (')' == expression[i]) | ||
| 865 | { | ||
| 866 | openedCount--; | ||
| 867 | } | ||
| 868 | |||
| 869 | if (openedCount == 0) | ||
| 870 | { | ||
| 871 | endingParen = i; | ||
| 872 | break; | ||
| 873 | } | ||
| 874 | } | ||
| 875 | |||
| 876 | if (-1 == endingParen) | ||
| 877 | { | ||
| 878 | throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 879 | } | ||
| 880 | token = expression.Substring(0, endingParen + 1); | ||
| 881 | |||
| 882 | // Advance past this variable | ||
| 883 | expression = expression.Substring(endingParen + 1).Trim(); | ||
| 884 | } | ||
| 885 | else | ||
| 886 | { | ||
| 887 | // Cut the token off at the next equal, space, inequality operator, | ||
| 888 | // or end of string, whichever comes first | ||
| 889 | var space = expression.IndexOf(" ", StringComparison.Ordinal); | ||
| 890 | var equals = expression.IndexOf("=", StringComparison.Ordinal); | ||
| 891 | var lessThan = expression.IndexOf("<", StringComparison.Ordinal); | ||
| 892 | var lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal); | ||
| 893 | var greaterThan = expression.IndexOf(">", StringComparison.Ordinal); | ||
| 894 | var greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal); | ||
| 895 | var notEquals = expression.IndexOf("!=", StringComparison.Ordinal); | ||
| 896 | var equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal); | ||
| 897 | int closingIndex; | ||
| 898 | |||
| 899 | if (space == -1) | ||
| 900 | { | ||
| 901 | space = Int32.MaxValue; | ||
| 902 | } | ||
| 903 | |||
| 904 | if (equals == -1) | ||
| 905 | { | ||
| 906 | equals = Int32.MaxValue; | ||
| 907 | } | ||
| 908 | |||
| 909 | if (lessThan == -1) | ||
| 910 | { | ||
| 911 | lessThan = Int32.MaxValue; | ||
| 912 | } | ||
| 913 | |||
| 914 | if (lessThanEquals == -1) | ||
| 915 | { | ||
| 916 | lessThanEquals = Int32.MaxValue; | ||
| 917 | } | ||
| 918 | |||
| 919 | if (greaterThan == -1) | ||
| 920 | { | ||
| 921 | greaterThan = Int32.MaxValue; | ||
| 922 | } | ||
| 923 | |||
| 924 | if (greaterThanEquals == -1) | ||
| 925 | { | ||
| 926 | greaterThanEquals = Int32.MaxValue; | ||
| 927 | } | ||
| 928 | |||
| 929 | if (notEquals == -1) | ||
| 930 | { | ||
| 931 | notEquals = Int32.MaxValue; | ||
| 932 | } | ||
| 933 | |||
| 934 | if (equalsNoCase == -1) | ||
| 935 | { | ||
| 936 | equalsNoCase = Int32.MaxValue; | ||
| 937 | } | ||
| 938 | |||
| 939 | closingIndex = Math.Min(space, Math.Min(equals, Math.Min(lessThan, Math.Min(lessThanEquals, Math.Min(greaterThan, Math.Min(greaterThanEquals, Math.Min(equalsNoCase, notEquals))))))); | ||
| 940 | |||
| 941 | if (Int32.MaxValue == closingIndex) | ||
| 942 | { | ||
| 943 | closingIndex = expression.Length; | ||
| 944 | } | ||
| 945 | |||
| 946 | // If the index is 0, we hit an operator, so return it | ||
| 947 | if (0 == closingIndex) | ||
| 948 | { | ||
| 949 | // Length 2 operators | ||
| 950 | if (closingIndex == lessThanEquals || closingIndex == greaterThanEquals || closingIndex == notEquals || closingIndex == equalsNoCase) | ||
| 951 | { | ||
| 952 | closingIndex = 2; | ||
| 953 | } | ||
| 954 | else // Length 1 operators | ||
| 955 | { | ||
| 956 | closingIndex = 1; | ||
| 957 | } | ||
| 958 | } | ||
| 959 | |||
| 960 | // Cut out the new token | ||
| 961 | token = expression.Substring(0, closingIndex).Trim(); | ||
| 962 | expression = expression.Substring(closingIndex).Trim(); | ||
| 963 | } | ||
| 964 | |||
| 965 | return token; | ||
| 966 | } | ||
| 967 | |||
| 968 | /// <summary> | ||
| 969 | /// Gets the value for a variable. | ||
| 970 | /// </summary> | ||
| 971 | /// <param name="state"></param> | ||
| 972 | /// <param name="originalExpression">Original expression for error message.</param> | ||
| 973 | /// <param name="variable">Variable to evaluate.</param> | ||
| 974 | /// <returns>Value of variable.</returns> | ||
| 975 | private string EvaluateVariable(ProcessingState state, string originalExpression, string variable) | ||
| 976 | { | ||
| 977 | // By default it's a literal and will only be evaluated if it | ||
| 978 | // matches the variable format | ||
| 979 | var varValue = variable; | ||
| 980 | |||
| 981 | if (variable.StartsWith("$(", StringComparison.Ordinal)) | ||
| 982 | { | ||
| 983 | try | ||
| 984 | { | ||
| 985 | varValue = state.Helper.PreprocessString(state.Context, variable); | ||
| 986 | } | ||
| 987 | catch (ArgumentNullException) | ||
| 988 | { | ||
| 989 | // non-existent variables are expected | ||
| 990 | varValue = null; | ||
| 991 | } | ||
| 992 | } | ||
| 993 | else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1) | ||
| 994 | { | ||
| 995 | // make sure it doesn't contain parenthesis | ||
| 996 | throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 997 | } | ||
| 998 | else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1) | ||
| 999 | { | ||
| 1000 | // shouldn't contain quotes | ||
| 1001 | throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | return varValue; | ||
| 1005 | } | ||
| 1006 | |||
| 1007 | /// <summary> | ||
| 1008 | /// Gets the left side value, operator, and right side value of an expression. | ||
| 1009 | /// </summary> | ||
| 1010 | /// <param name="state"></param> | ||
| 1011 | /// <param name="originalExpression">Original expression to evaluate.</param> | ||
| 1012 | /// <param name="expression">Expression modified while processing.</param> | ||
| 1013 | /// <param name="leftValue">Left side value from expression.</param> | ||
| 1014 | /// <param name="operation">Operation in expression.</param> | ||
| 1015 | /// <param name="rightValue">Right side value from expression.</param> | ||
| 1016 | private void GetNameValuePair(ProcessingState state, string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue) | ||
| 1017 | { | ||
| 1018 | leftValue = this.GetNextToken(state, originalExpression, ref expression, out var stringLiteral); | ||
| 1019 | |||
| 1020 | // If it wasn't a string literal, evaluate it | ||
| 1021 | if (!stringLiteral) | ||
| 1022 | { | ||
| 1023 | leftValue = this.EvaluateVariable(state, originalExpression, leftValue); | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | // Get the operation | ||
| 1027 | operation = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); | ||
| 1028 | if (IsOperator(operation)) | ||
| 1029 | { | ||
| 1030 | if (stringLiteral) | ||
| 1031 | { | ||
| 1032 | throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 1033 | } | ||
| 1034 | |||
| 1035 | rightValue = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); | ||
| 1036 | |||
| 1037 | // If it wasn't a string literal, evaluate it | ||
| 1038 | if (!stringLiteral) | ||
| 1039 | { | ||
| 1040 | rightValue = this.EvaluateVariable(state, originalExpression, rightValue); | ||
| 1041 | } | ||
| 1042 | } | ||
| 1043 | else | ||
| 1044 | { | ||
| 1045 | // Prepend the token back on the expression since it wasn't an operator | ||
| 1046 | // and put the quotes back on the literal if necessary | ||
| 1047 | |||
| 1048 | if (stringLiteral) | ||
| 1049 | { | ||
| 1050 | operation = "\"" + operation + "\""; | ||
| 1051 | } | ||
| 1052 | expression = (operation + " " + expression).Trim(); | ||
| 1053 | |||
| 1054 | // If no operator, just check for existence | ||
| 1055 | operation = ""; | ||
| 1056 | rightValue = ""; | ||
| 1057 | } | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | /// <summary> | ||
| 1061 | /// Evaluates an expression. | ||
| 1062 | /// </summary> | ||
| 1063 | /// <param name="state"></param> | ||
| 1064 | /// <param name="originalExpression">Original expression to evaluate.</param> | ||
| 1065 | /// <param name="expression">Expression modified while processing.</param> | ||
| 1066 | /// <returns>true if expression evaluates to true.</returns> | ||
| 1067 | private bool EvaluateAtomicExpression(ProcessingState state, string originalExpression, ref string expression) | ||
| 1068 | { | ||
| 1069 | // Quick test to see if the first token is a variable | ||
| 1070 | var startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal); | ||
| 1071 | this.GetNameValuePair(state, originalExpression, ref expression, out var leftValue, out var operation, out var rightValue); | ||
| 1072 | |||
| 1073 | var expressionValue = false; | ||
| 1074 | |||
| 1075 | // If the variables don't exist, they were evaluated to null | ||
| 1076 | if (null == leftValue || null == rightValue) | ||
| 1077 | { | ||
| 1078 | if (operation.Length > 0) | ||
| 1079 | { | ||
| 1080 | throw new WixException(ErrorMessages.ExpectedVariable(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 1081 | } | ||
| 1082 | |||
| 1083 | // false expression | ||
| 1084 | } | ||
| 1085 | else if (operation.Length == 0) | ||
| 1086 | { | ||
| 1087 | // There is no right side of the equation. | ||
| 1088 | // If the variable was evaluated, it exists, so the expression is true | ||
| 1089 | if (startsWithVariable) | ||
| 1090 | { | ||
| 1091 | expressionValue = true; | ||
| 1092 | } | ||
| 1093 | else | ||
| 1094 | { | ||
| 1095 | throw new WixException(ErrorMessages.UnexpectedLiteral(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 1096 | } | ||
| 1097 | } | ||
| 1098 | else | ||
| 1099 | { | ||
| 1100 | leftValue = leftValue.Trim(); | ||
| 1101 | rightValue = rightValue.Trim(); | ||
| 1102 | if ("=" == operation) | ||
| 1103 | { | ||
| 1104 | if (leftValue == rightValue) | ||
| 1105 | { | ||
| 1106 | expressionValue = true; | ||
| 1107 | } | ||
| 1108 | } | ||
| 1109 | else if ("!=" == operation) | ||
| 1110 | { | ||
| 1111 | if (leftValue != rightValue) | ||
| 1112 | { | ||
| 1113 | expressionValue = true; | ||
| 1114 | } | ||
| 1115 | } | ||
| 1116 | else if ("~=" == operation) | ||
| 1117 | { | ||
| 1118 | if (String.Equals(leftValue, rightValue, StringComparison.OrdinalIgnoreCase)) | ||
| 1119 | { | ||
| 1120 | expressionValue = true; | ||
| 1121 | } | ||
| 1122 | } | ||
| 1123 | else | ||
| 1124 | { | ||
| 1125 | // Convert the numbers from strings | ||
| 1126 | int rightInt; | ||
| 1127 | int leftInt; | ||
| 1128 | try | ||
| 1129 | { | ||
| 1130 | rightInt = Int32.Parse(rightValue, CultureInfo.InvariantCulture); | ||
| 1131 | leftInt = Int32.Parse(leftValue, CultureInfo.InvariantCulture); | ||
| 1132 | } | ||
| 1133 | catch (FormatException) | ||
| 1134 | { | ||
| 1135 | throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 1136 | } | ||
| 1137 | catch (OverflowException) | ||
| 1138 | { | ||
| 1139 | throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 1140 | } | ||
| 1141 | |||
| 1142 | // Compare the numbers | ||
| 1143 | if ("<" == operation && leftInt < rightInt || | ||
| 1144 | "<=" == operation && leftInt <= rightInt || | ||
| 1145 | ">" == operation && leftInt > rightInt || | ||
| 1146 | ">=" == operation && leftInt >= rightInt) | ||
| 1147 | { | ||
| 1148 | expressionValue = true; | ||
| 1149 | } | ||
| 1150 | } | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | return expressionValue; | ||
| 1154 | } | ||
| 1155 | |||
| 1156 | /// <summary> | ||
| 1157 | /// Gets a sub-expression in parenthesis. | ||
| 1158 | /// </summary> | ||
| 1159 | /// <param name="state"></param> | ||
| 1160 | /// <param name="originalExpression">Original expression to evaluate.</param> | ||
| 1161 | /// <param name="expression">Expression modified while processing.</param> | ||
| 1162 | /// <param name="endSubExpression">Index of end of sub-expression.</param> | ||
| 1163 | /// <returns>Sub-expression in parenthesis.</returns> | ||
| 1164 | private string GetParenthesisExpression(ProcessingState state, string originalExpression, string expression, out int endSubExpression) | ||
| 1165 | { | ||
| 1166 | endSubExpression = 0; | ||
| 1167 | |||
| 1168 | // if the expression doesn't start with parenthesis, leave it alone | ||
| 1169 | if (!expression.StartsWith("(", StringComparison.Ordinal)) | ||
| 1170 | { | ||
| 1171 | return expression; | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | // search for the end of the expression with the matching paren | ||
| 1175 | var openParenIndex = 0; | ||
| 1176 | var closeParenIndex = 1; | ||
| 1177 | while (openParenIndex != -1 && openParenIndex < closeParenIndex) | ||
| 1178 | { | ||
| 1179 | closeParenIndex = expression.IndexOf(')', closeParenIndex); | ||
| 1180 | if (closeParenIndex == -1) | ||
| 1181 | { | ||
| 1182 | throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 1183 | } | ||
| 1184 | |||
| 1185 | if (InsideQuotes(expression, closeParenIndex)) | ||
| 1186 | { | ||
| 1187 | // ignore stuff inside quotes (it's a string literal) | ||
| 1188 | } | ||
| 1189 | else | ||
| 1190 | { | ||
| 1191 | // Look to see if there is another open paren before the close paren | ||
| 1192 | // and skip over the open parens while they are in a string literal | ||
| 1193 | do | ||
| 1194 | { | ||
| 1195 | openParenIndex++; | ||
| 1196 | openParenIndex = expression.IndexOf('(', openParenIndex, closeParenIndex - openParenIndex); | ||
| 1197 | } | ||
| 1198 | while (InsideQuotes(expression, openParenIndex)); | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | // Advance past the closing paren | ||
| 1202 | closeParenIndex++; | ||
| 1203 | } | ||
| 1204 | |||
| 1205 | endSubExpression = closeParenIndex; | ||
| 1206 | |||
| 1207 | // Return the expression minus the parenthesis | ||
| 1208 | return expression.Substring(1, closeParenIndex - 2); | ||
| 1209 | } | ||
| 1210 | |||
| 1211 | /// <summary> | ||
| 1212 | /// Updates expression based on operation. | ||
| 1213 | /// </summary> | ||
| 1214 | /// <param name="state"></param> | ||
| 1215 | /// <param name="currentValue">State to update.</param> | ||
| 1216 | /// <param name="operation">Operation to apply to current value.</param> | ||
| 1217 | /// <param name="prevResult">Previous result.</param> | ||
| 1218 | private void UpdateExpressionValue(ProcessingState state, ref bool currentValue, PreprocessorOperation operation, bool prevResult) | ||
| 1219 | { | ||
| 1220 | switch (operation) | ||
| 1221 | { | ||
| 1222 | case PreprocessorOperation.And: | ||
| 1223 | currentValue = currentValue && prevResult; | ||
| 1224 | break; | ||
| 1225 | case PreprocessorOperation.Or: | ||
| 1226 | currentValue = currentValue || prevResult; | ||
| 1227 | break; | ||
| 1228 | case PreprocessorOperation.Not: | ||
| 1229 | currentValue = !currentValue; | ||
| 1230 | break; | ||
| 1231 | default: | ||
| 1232 | throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(state.Context.CurrentSourceLineNumber, operation.ToString())); | ||
| 1233 | } | ||
| 1234 | } | ||
| 1235 | |||
| 1236 | /// <summary> | ||
| 1237 | /// Evaluate an expression. | ||
| 1238 | /// </summary> | ||
| 1239 | /// <param name="state"></param> | ||
| 1240 | /// <param name="expression">Expression to evaluate.</param> | ||
| 1241 | /// <returns>Boolean result of expression.</returns> | ||
| 1242 | private bool EvaluateExpression(ProcessingState state, string expression) | ||
| 1243 | { | ||
| 1244 | var tmpExpression = expression; | ||
| 1245 | return this.EvaluateExpressionRecurse(state, expression, ref tmpExpression, PreprocessorOperation.And, true); | ||
| 1246 | } | ||
| 1247 | |||
| 1248 | /// <summary> | ||
| 1249 | /// Recurse through the expression to evaluate if it is true or false. | ||
| 1250 | /// The expression is evaluated left to right. | ||
| 1251 | /// The expression is case-sensitive (converted to upper case) with the | ||
| 1252 | /// following exceptions: variable names and keywords (and, not, or). | ||
| 1253 | /// Comparisons with = and != are string comparisons. | ||
| 1254 | /// Comparisons with inequality operators must be done on valid integers. | ||
| 1255 | /// | ||
| 1256 | /// The operator precedence is: | ||
| 1257 | /// "" | ||
| 1258 | /// () | ||
| 1259 | /// <, >, <=, >=, =, != | ||
| 1260 | /// Not | ||
| 1261 | /// And, Or | ||
| 1262 | /// | ||
| 1263 | /// Valid expressions include: | ||
| 1264 | /// not $(var.B) or not $(var.C) | ||
| 1265 | /// (($(var.A))and $(var.B) ="2")or Not((($(var.C))) and $(var.A)) | ||
| 1266 | /// (($(var.A)) and $(var.B) = " 3 ") or $(var.C) | ||
| 1267 | /// $(var.A) and $(var.C) = "3" or $(var.C) and $(var.D) = $(env.windir) | ||
| 1268 | /// $(var.A) and $(var.B)>2 or $(var.B) <= 2 | ||
| 1269 | /// $(var.A) != "2" | ||
| 1270 | /// </summary> | ||
| 1271 | /// <param name="state"></param> | ||
| 1272 | /// <param name="originalExpression">The original expression</param> | ||
| 1273 | /// <param name="expression">The expression currently being evaluated</param> | ||
| 1274 | /// <param name="prevResultOperation">The operation to apply to this result</param> | ||
| 1275 | /// <param name="prevResult">The previous result to apply to this result</param> | ||
| 1276 | /// <returns>Boolean to indicate if the expression is true or false</returns> | ||
| 1277 | private bool EvaluateExpressionRecurse(ProcessingState state, string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult) | ||
| 1278 | { | ||
| 1279 | var expressionValue = false; | ||
| 1280 | expression = expression.Trim(); | ||
| 1281 | if (expression.Length == 0) | ||
| 1282 | { | ||
| 1283 | throw new WixException(ErrorMessages.UnexpectedEmptySubexpression(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 1284 | } | ||
| 1285 | |||
| 1286 | // If the expression starts with parenthesis, evaluate it | ||
| 1287 | if (expression.IndexOf('(') == 0) | ||
| 1288 | { | ||
| 1289 | var subExpression = this.GetParenthesisExpression(state, originalExpression, expression, out var endSubExpressionIndex); | ||
| 1290 | expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref subExpression, PreprocessorOperation.And, true); | ||
| 1291 | |||
| 1292 | // Now get the rest of the expression that hasn't been evaluated | ||
| 1293 | expression = expression.Substring(endSubExpressionIndex).Trim(); | ||
| 1294 | } | ||
| 1295 | else | ||
| 1296 | { | ||
| 1297 | // Check for NOT | ||
| 1298 | if (StartsWithKeyword(expression, PreprocessorOperation.Not)) | ||
| 1299 | { | ||
| 1300 | expression = expression.Substring(3).Trim(); | ||
| 1301 | if (expression.Length == 0) | ||
| 1302 | { | ||
| 1303 | throw new WixException(ErrorMessages.ExpectedExpressionAfterNot(state.Context.CurrentSourceLineNumber, originalExpression)); | ||
| 1304 | } | ||
| 1305 | |||
| 1306 | expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Not, true); | ||
| 1307 | } | ||
| 1308 | else // Expect a literal | ||
| 1309 | { | ||
| 1310 | expressionValue = this.EvaluateAtomicExpression(state, originalExpression, ref expression); | ||
| 1311 | |||
| 1312 | // Expect the literal that was just evaluated to already be cut off | ||
| 1313 | } | ||
| 1314 | } | ||
| 1315 | this.UpdateExpressionValue(state, ref expressionValue, prevResultOperation, prevResult); | ||
| 1316 | |||
| 1317 | // If there's still an expression left, it must start with AND or OR. | ||
| 1318 | if (expression.Trim().Length > 0) | ||
| 1319 | { | ||
| 1320 | if (StartsWithKeyword(expression, PreprocessorOperation.And)) | ||
| 1321 | { | ||
| 1322 | expression = expression.Substring(3); | ||
| 1323 | return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.And, expressionValue); | ||
| 1324 | } | ||
| 1325 | else if (StartsWithKeyword(expression, PreprocessorOperation.Or)) | ||
| 1326 | { | ||
| 1327 | expression = expression.Substring(2); | ||
| 1328 | return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Or, expressionValue); | ||
| 1329 | } | ||
| 1330 | else | ||
| 1331 | { | ||
| 1332 | throw new WixException(ErrorMessages.InvalidSubExpression(state.Context.CurrentSourceLineNumber, expression, originalExpression)); | ||
| 1333 | } | ||
| 1334 | } | ||
| 1335 | |||
| 1336 | return expressionValue; | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | /// <summary> | ||
| 1340 | /// Update the current line number with the reader's current state. | ||
| 1341 | /// </summary> | ||
| 1342 | /// <param name="state"></param> | ||
| 1343 | /// <param name="reader">The xml reader for the preprocessor.</param> | ||
| 1344 | /// <param name="offset">This is the artificial offset of the line numbers from the reader. Used for the foreach processing.</param> | ||
| 1345 | private void UpdateCurrentLineNumber(ProcessingState state, XmlReader reader, int offset) | ||
| 1346 | { | ||
| 1347 | var lineInfoReader = reader as IXmlLineInfo; | ||
| 1348 | if (null != lineInfoReader) | ||
| 1349 | { | ||
| 1350 | var newLine = lineInfoReader.LineNumber + offset; | ||
| 1351 | |||
| 1352 | if (state.Context.CurrentSourceLineNumber.LineNumber != newLine) | ||
| 1353 | { | ||
| 1354 | state.Context.CurrentSourceLineNumber = new SourceLineNumber(state.Context.CurrentSourceLineNumber.FileName, state.Context.CurrentSourceLineNumber.Parent, newLine); | ||
| 1355 | } | ||
| 1356 | } | ||
| 1357 | } | ||
| 1358 | |||
| 1359 | /// <summary> | ||
| 1360 | /// Pushes a file name on the stack of included files. | ||
| 1361 | /// </summary> | ||
| 1362 | /// <param name="state"></param> | ||
| 1363 | /// <param name="fileName">Name to push on to the stack of included files.</param> | ||
| 1364 | private void PushInclude(ProcessingState state, string fileName) | ||
| 1365 | { | ||
| 1366 | if (1023 < state.CurrentFileStack.Count) | ||
| 1367 | { | ||
| 1368 | throw new WixException(ErrorMessages.TooDeeplyIncluded(state.Context.CurrentSourceLineNumber, state.CurrentFileStack.Count)); | ||
| 1369 | } | ||
| 1370 | |||
| 1371 | var path = Path.GetFullPath(fileName); | ||
| 1372 | |||
| 1373 | state.CurrentFileStack.Push(path); | ||
| 1374 | state.SourceStack.Push(state.Context.CurrentSourceLineNumber); | ||
| 1375 | state.Context.CurrentSourceLineNumber = new SourceLineNumber(path, state.Context.CurrentSourceLineNumber); | ||
| 1376 | state.IncludeNextStack.Push(true); | ||
| 1377 | } | ||
| 1378 | |||
| 1379 | /// <summary> | ||
| 1380 | /// Pops a file name from the stack of included files. | ||
| 1381 | /// </summary> | ||
| 1382 | private void PopInclude(ProcessingState state) | ||
| 1383 | { | ||
| 1384 | state.Context.CurrentSourceLineNumber = state.SourceStack.Pop(); | ||
| 1385 | |||
| 1386 | state.CurrentFileStack.Pop(); | ||
| 1387 | state.IncludeNextStack.Pop(); | ||
| 1388 | } | ||
| 1389 | |||
| 1390 | /// <summary> | ||
| 1391 | /// Go through search paths, looking for a matching include file. | ||
| 1392 | /// Start the search in the directory of the source file, then go | ||
| 1393 | /// through the search paths in the order given on the command line | ||
| 1394 | /// (leftmost first, ...). | ||
| 1395 | /// </summary> | ||
| 1396 | /// <param name="state"></param> | ||
| 1397 | /// <param name="includePath">User-specified path to the included file (usually just the file name).</param> | ||
| 1398 | /// <returns>Returns a FileInfo for the found include file, or null if the file cannot be found.</returns> | ||
| 1399 | private string GetIncludeFile(ProcessingState state, string includePath) | ||
| 1400 | { | ||
| 1401 | string finalIncludePath = null; | ||
| 1402 | |||
| 1403 | includePath = includePath.Trim(); | ||
| 1404 | |||
| 1405 | // remove quotes (only if they match) | ||
| 1406 | if ((includePath.StartsWith("\"", StringComparison.Ordinal) && includePath.EndsWith("\"", StringComparison.Ordinal)) || | ||
| 1407 | (includePath.StartsWith("'", StringComparison.Ordinal) && includePath.EndsWith("'", StringComparison.Ordinal))) | ||
| 1408 | { | ||
| 1409 | includePath = includePath.Substring(1, includePath.Length - 2); | ||
| 1410 | } | ||
| 1411 | |||
| 1412 | // check if the include file is a full path | ||
| 1413 | if (Path.IsPathRooted(includePath)) | ||
| 1414 | { | ||
| 1415 | if (File.Exists(includePath)) | ||
| 1416 | { | ||
| 1417 | finalIncludePath = includePath; | ||
| 1418 | } | ||
| 1419 | } | ||
| 1420 | else // relative path | ||
| 1421 | { | ||
| 1422 | // build a string to test the directory containing the source file first | ||
| 1423 | var currentFolder = state.CurrentFileStack.Peek(); | ||
| 1424 | var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder), includePath); | ||
| 1425 | |||
| 1426 | // test the source file directory | ||
| 1427 | if (File.Exists(includeTestPath)) | ||
| 1428 | { | ||
| 1429 | finalIncludePath = includeTestPath; | ||
| 1430 | } | ||
| 1431 | else if (state.Context.IncludeSearchPaths != null) // test all search paths in the order specified on the command line | ||
| 1432 | { | ||
| 1433 | foreach (var includeSearchPath in state.Context.IncludeSearchPaths) | ||
| 1434 | { | ||
| 1435 | // if the path exists, we have found the final string | ||
| 1436 | includeTestPath = Path.Combine(includeSearchPath, includePath); | ||
| 1437 | if (File.Exists(includeTestPath)) | ||
| 1438 | { | ||
| 1439 | finalIncludePath = includeTestPath; | ||
| 1440 | break; | ||
| 1441 | } | ||
| 1442 | } | ||
| 1443 | } | ||
| 1444 | } | ||
| 1445 | |||
| 1446 | return finalIncludePath; | ||
| 1447 | } | ||
| 1448 | |||
| 1449 | private void PreProcess(ProcessingState state) | ||
| 1450 | { | ||
| 1451 | if (state.Context.Extensions == null) | ||
| 1452 | { | ||
| 1453 | return; | ||
| 1454 | } | ||
| 1455 | |||
| 1456 | foreach (var extension in state.Context.Extensions) | ||
| 1457 | { | ||
| 1458 | if (extension.Prefixes != null) | ||
| 1459 | { | ||
| 1460 | foreach (var prefix in extension.Prefixes) | ||
| 1461 | { | ||
| 1462 | if (!state.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension)) | ||
| 1463 | { | ||
| 1464 | state.ExtensionsByPrefix.Add(prefix, extension); | ||
| 1465 | } | ||
| 1466 | else | ||
| 1467 | { | ||
| 1468 | this.Messaging.Write(ErrorMessages.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString())); | ||
| 1469 | } | ||
| 1470 | } | ||
| 1471 | } | ||
| 1472 | |||
| 1473 | extension.PrePreprocess(state.Context); | ||
| 1474 | } | ||
| 1475 | } | ||
| 1476 | |||
| 1477 | private void PostProcess(ProcessingState state, IPreprocessResult result) | ||
| 1478 | { | ||
| 1479 | if (state.Context.Extensions == null) | ||
| 1480 | { | ||
| 1481 | return; | ||
| 1482 | } | ||
| 1483 | |||
| 1484 | foreach (var extension in state.Context.Extensions) | ||
| 1485 | { | ||
| 1486 | extension.PostPreprocess(result); | ||
| 1487 | } | ||
| 1488 | } | ||
| 1489 | |||
| 1490 | private class ProcessingState | ||
| 1491 | { | ||
| 1492 | public ProcessingState(IServiceProvider serviceProvider, IPreprocessContext context) | ||
| 1493 | { | ||
| 1494 | var path = Path.GetFullPath(context.SourcePath); | ||
| 1495 | |||
| 1496 | this.Context = context; | ||
| 1497 | this.Context.CurrentSourceLineNumber = new SourceLineNumber(path); | ||
| 1498 | this.Context.Variables = this.Context.Variables == null ? new Dictionary<string, string>() : new Dictionary<string, string>(this.Context.Variables); | ||
| 1499 | |||
| 1500 | this.Helper = serviceProvider.GetService<IPreprocessHelper>(); | ||
| 1501 | } | ||
| 1502 | |||
| 1503 | public IPreprocessContext Context { get; } | ||
| 1504 | |||
| 1505 | public IPreprocessHelper Helper { get; } | ||
| 1506 | |||
| 1507 | public List<IIncludedFile> IncludedFiles { get; } = new List<IIncludedFile>(); | ||
| 1508 | |||
| 1509 | public XDocument Output { get; } = new XDocument(); | ||
| 1510 | |||
| 1511 | public Stack<string> CurrentFileStack { get; } = new Stack<string>(); | ||
| 1512 | |||
| 1513 | public Dictionary<string, IPreprocessorExtension> ExtensionsByPrefix { get; } = new Dictionary<string, IPreprocessorExtension>(); | ||
| 1514 | |||
| 1515 | public Stack<bool> IncludeNextStack { get; } = new Stack<bool>(); | ||
| 1516 | |||
| 1517 | public Stack<SourceLineNumber> SourceStack { get; } = new Stack<SourceLineNumber>(); | ||
| 1518 | } | ||
| 1519 | } | ||
| 1520 | } | ||
diff --git a/src/wix/WixToolset.Core/Properties/AssemblyInfo.cs b/src/wix/WixToolset.Core/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..81274e3f --- /dev/null +++ b/src/wix/WixToolset.Core/Properties/AssemblyInfo.cs | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | using System; | ||
| 4 | using System.Reflection; | ||
| 5 | using System.Runtime.InteropServices; | ||
| 6 | |||
| 7 | [assembly: AssemblyCulture("")] | ||
| 8 | [assembly: CLSCompliant(false)] | ||
| 9 | [assembly: ComVisible(false)] | ||
diff --git a/src/wix/WixToolset.Core/ResolveContext.cs b/src/wix/WixToolset.Core/ResolveContext.cs new file mode 100644 index 00000000..638c8079 --- /dev/null +++ b/src/wix/WixToolset.Core/ResolveContext.cs | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Threading; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class ResolveContext : IResolveContext | ||
| 14 | { | ||
| 15 | internal ResolveContext(IServiceProvider serviceProvider) | ||
| 16 | { | ||
| 17 | this.ServiceProvider = serviceProvider; | ||
| 18 | } | ||
| 19 | |||
| 20 | public IServiceProvider ServiceProvider { get; } | ||
| 21 | |||
| 22 | public IReadOnlyCollection<IBindPath> BindPaths { get; set; } | ||
| 23 | |||
| 24 | public IReadOnlyCollection<IResolverExtension> Extensions { get; set; } | ||
| 25 | |||
| 26 | public IReadOnlyCollection<IExtensionData> ExtensionData { get; set; } | ||
| 27 | |||
| 28 | public IReadOnlyCollection<string> FilterCultures { get; set; } | ||
| 29 | |||
| 30 | public string IntermediateFolder { get; set; } | ||
| 31 | |||
| 32 | public Intermediate IntermediateRepresentation { get; set; } | ||
| 33 | |||
| 34 | public IReadOnlyCollection<Localization> Localizations { get; set; } | ||
| 35 | |||
| 36 | public IVariableResolver VariableResolver { get; set; } | ||
| 37 | |||
| 38 | public bool AllowUnresolvedVariables { get; set; } | ||
| 39 | |||
| 40 | public CancellationToken CancellationToken { get; set; } | ||
| 41 | } | ||
| 42 | } | ||
diff --git a/src/wix/WixToolset.Core/ResolveFileResult.cs b/src/wix/WixToolset.Core/ResolveFileResult.cs new file mode 100644 index 00000000..f6e201d4 --- /dev/null +++ b/src/wix/WixToolset.Core/ResolveFileResult.cs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | internal class ResolveFileResult : IResolveFileResult | ||
| 9 | { | ||
| 10 | public string Path { get; set; } | ||
| 11 | |||
| 12 | public IReadOnlyCollection<string> CheckedPaths { get; set; } | ||
| 13 | } | ||
| 14 | } | ||
diff --git a/src/wix/WixToolset.Core/ResolveResult.cs b/src/wix/WixToolset.Core/ResolveResult.cs new file mode 100644 index 00000000..fa8e09b7 --- /dev/null +++ b/src/wix/WixToolset.Core/ResolveResult.cs | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Extensibility.Data; | ||
| 8 | |||
| 9 | internal class ResolveResult : IResolveResult | ||
| 10 | { | ||
| 11 | public int? Codepage { get; set; } | ||
| 12 | |||
| 13 | public int? SummaryInformationCodepage { get; set; } | ||
| 14 | |||
| 15 | public int? PackageLcid { get; set; } | ||
| 16 | |||
| 17 | public IReadOnlyCollection<IDelayedField> DelayedFields { get; set; } | ||
| 18 | |||
| 19 | public IReadOnlyCollection<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; } | ||
| 20 | |||
| 21 | public Intermediate IntermediateRepresentation { get; set; } | ||
| 22 | } | ||
| 23 | } | ||
diff --git a/src/wix/WixToolset.Core/ResolvedCabinet.cs b/src/wix/WixToolset.Core/ResolvedCabinet.cs new file mode 100644 index 00000000..be04831f --- /dev/null +++ b/src/wix/WixToolset.Core/ResolvedCabinet.cs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Extensibility.Data; | ||
| 6 | |||
| 7 | /// <summary> | ||
| 8 | /// Data returned from build file manager ResolveCabinet callback. | ||
| 9 | /// </summary> | ||
| 10 | internal class ResolvedCabinet : IResolvedCabinet | ||
| 11 | { | ||
| 12 | /// <summary> | ||
| 13 | /// Gets or sets the build option for the resolved cabinet. | ||
| 14 | /// </summary> | ||
| 15 | public CabinetBuildOption BuildOption { get; set; } | ||
| 16 | |||
| 17 | /// <summary> | ||
| 18 | /// Gets or sets the path for the resolved cabinet. | ||
| 19 | /// </summary> | ||
| 20 | public string Path { get; set; } | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/wix/WixToolset.Core/Resolver.cs b/src/wix/WixToolset.Core/Resolver.cs new file mode 100644 index 00000000..e93f8e1b --- /dev/null +++ b/src/wix/WixToolset.Core/Resolver.cs | |||
| @@ -0,0 +1,304 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Globalization; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Core.Bind; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | using WixToolset.Extensibility; | ||
| 13 | using WixToolset.Extensibility.Data; | ||
| 14 | using WixToolset.Extensibility.Services; | ||
| 15 | |||
| 16 | /// <summary> | ||
| 17 | /// Resolver for the WiX toolset. | ||
| 18 | /// </summary> | ||
| 19 | internal class Resolver : IResolver | ||
| 20 | { | ||
| 21 | internal Resolver(IServiceProvider serviceProvider) | ||
| 22 | { | ||
| 23 | this.ServiceProvider = serviceProvider; | ||
| 24 | |||
| 25 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 26 | } | ||
| 27 | |||
| 28 | private IServiceProvider ServiceProvider { get; } | ||
| 29 | |||
| 30 | private IMessaging Messaging { get; } | ||
| 31 | |||
| 32 | public IResolveResult Resolve(IResolveContext context) | ||
| 33 | { | ||
| 34 | foreach (var extension in context.Extensions) | ||
| 35 | { | ||
| 36 | extension.PreResolve(context); | ||
| 37 | } | ||
| 38 | |||
| 39 | ResolveResult resolveResult = null; | ||
| 40 | try | ||
| 41 | { | ||
| 42 | var filteredLocalizations = FilterLocalizations(context); | ||
| 43 | |||
| 44 | var variableResolver = this.CreateVariableResolver(context, filteredLocalizations); | ||
| 45 | |||
| 46 | this.LocalizeUI(variableResolver, context.IntermediateRepresentation); | ||
| 47 | |||
| 48 | resolveResult = this.DoResolve(context, variableResolver); | ||
| 49 | |||
| 50 | var primaryLocalization = filteredLocalizations.FirstOrDefault(); | ||
| 51 | |||
| 52 | if (primaryLocalization != null) | ||
| 53 | { | ||
| 54 | this.TryGetCultureInfo(primaryLocalization.Culture, out var cultureInfo); | ||
| 55 | |||
| 56 | resolveResult.Codepage = primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; | ||
| 57 | |||
| 58 | resolveResult.SummaryInformationCodepage = primaryLocalization.SummaryInformationCodepage ?? primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; | ||
| 59 | |||
| 60 | resolveResult.PackageLcid = cultureInfo?.LCID; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | finally | ||
| 64 | { | ||
| 65 | foreach (var extension in context.Extensions) | ||
| 66 | { | ||
| 67 | extension.PostResolve(resolveResult); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | return resolveResult; | ||
| 72 | } | ||
| 73 | |||
| 74 | private ResolveResult DoResolve(IResolveContext context, IVariableResolver variableResolver) | ||
| 75 | { | ||
| 76 | var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch); | ||
| 77 | |||
| 78 | var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); | ||
| 79 | |||
| 80 | IReadOnlyCollection<DelayedField> delayedFields; | ||
| 81 | { | ||
| 82 | var command = new ResolveFieldsCommand(); | ||
| 83 | command.Messaging = this.Messaging; | ||
| 84 | command.BuildingPatch = buildingPatch; | ||
| 85 | command.VariableResolver = variableResolver; | ||
| 86 | command.BindPaths = context.BindPaths; | ||
| 87 | command.Extensions = context.Extensions; | ||
| 88 | command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 89 | command.IntermediateFolder = context.IntermediateFolder; | ||
| 90 | command.Intermediate = context.IntermediateRepresentation; | ||
| 91 | command.SupportDelayedResolution = true; | ||
| 92 | command.AllowUnresolvedVariables = context.AllowUnresolvedVariables; | ||
| 93 | command.Execute(); | ||
| 94 | |||
| 95 | delayedFields = command.DelayedFields; | ||
| 96 | } | ||
| 97 | |||
| 98 | #if TODO_PATCHING | ||
| 99 | if (context.IntermediateRepresentation.SubStorages != null) | ||
| 100 | { | ||
| 101 | foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages) | ||
| 102 | { | ||
| 103 | var command = new ResolveFieldsCommand(); | ||
| 104 | command.BuildingPatch = buildingPatch; | ||
| 105 | command.BindVariableResolver = context.WixVariableResolver; | ||
| 106 | command.BindPaths = context.BindPaths; | ||
| 107 | command.Extensions = context.Extensions; | ||
| 108 | command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
| 109 | command.IntermediateFolder = context.IntermediateFolder; | ||
| 110 | command.Intermediate = context.IntermediateRepresentation; | ||
| 111 | command.SupportDelayedResolution = false; | ||
| 112 | command.Execute(); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | #endif | ||
| 116 | |||
| 117 | var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); | ||
| 118 | |||
| 119 | context.IntermediateRepresentation.UpdateLevel(IntermediateLevels.Resolved); | ||
| 120 | |||
| 121 | return new ResolveResult | ||
| 122 | { | ||
| 123 | ExpectedEmbeddedFiles = expectedEmbeddedFiles, | ||
| 124 | DelayedFields = delayedFields, | ||
| 125 | IntermediateRepresentation = context.IntermediateRepresentation | ||
| 126 | }; | ||
| 127 | } | ||
| 128 | |||
| 129 | /// <summary> | ||
| 130 | /// Localize dialogs and controls. | ||
| 131 | /// </summary> | ||
| 132 | private void LocalizeUI(IVariableResolver variableResolver, Intermediate intermediate) | ||
| 133 | { | ||
| 134 | foreach (var section in intermediate.Sections) | ||
| 135 | { | ||
| 136 | foreach (var symbol in section.Symbols.OfType<DialogSymbol>()) | ||
| 137 | { | ||
| 138 | if (variableResolver.TryGetLocalizedControl(symbol.Id.Id, null, out var localizedControl)) | ||
| 139 | { | ||
| 140 | if (CompilerConstants.IntegerNotSet != localizedControl.X) | ||
| 141 | { | ||
| 142 | symbol.HCentering = localizedControl.X; | ||
| 143 | } | ||
| 144 | |||
| 145 | if (CompilerConstants.IntegerNotSet != localizedControl.Y) | ||
| 146 | { | ||
| 147 | symbol.VCentering = localizedControl.Y; | ||
| 148 | } | ||
| 149 | |||
| 150 | if (CompilerConstants.IntegerNotSet != localizedControl.Width) | ||
| 151 | { | ||
| 152 | symbol.Width = localizedControl.Width; | ||
| 153 | } | ||
| 154 | |||
| 155 | if (CompilerConstants.IntegerNotSet != localizedControl.Height) | ||
| 156 | { | ||
| 157 | symbol.Height = localizedControl.Height; | ||
| 158 | } | ||
| 159 | |||
| 160 | symbol.RightAligned |= localizedControl.RightAligned; | ||
| 161 | symbol.RightToLeft |= localizedControl.RightToLeft; | ||
| 162 | symbol.LeftScroll |= localizedControl.LeftScroll; | ||
| 163 | |||
| 164 | if (!String.IsNullOrEmpty(localizedControl.Text)) | ||
| 165 | { | ||
| 166 | symbol.Title = localizedControl.Text; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | foreach (var symbol in section.Symbols.OfType<ControlSymbol>()) | ||
| 172 | { | ||
| 173 | if (variableResolver.TryGetLocalizedControl(symbol.DialogRef, symbol.Control, out var localizedControl)) | ||
| 174 | { | ||
| 175 | if (CompilerConstants.IntegerNotSet != localizedControl.X) | ||
| 176 | { | ||
| 177 | symbol.X = localizedControl.X; | ||
| 178 | } | ||
| 179 | |||
| 180 | if (CompilerConstants.IntegerNotSet != localizedControl.Y) | ||
| 181 | { | ||
| 182 | symbol.Y = localizedControl.Y; | ||
| 183 | } | ||
| 184 | |||
| 185 | if (CompilerConstants.IntegerNotSet != localizedControl.Width) | ||
| 186 | { | ||
| 187 | symbol.Width = localizedControl.Width; | ||
| 188 | } | ||
| 189 | |||
| 190 | if (CompilerConstants.IntegerNotSet != localizedControl.Height) | ||
| 191 | { | ||
| 192 | symbol.Height = localizedControl.Height; | ||
| 193 | } | ||
| 194 | |||
| 195 | symbol.RightAligned |= localizedControl.RightAligned; | ||
| 196 | symbol.RightToLeft |= localizedControl.RightToLeft; | ||
| 197 | symbol.LeftScroll |= localizedControl.LeftScroll; | ||
| 198 | |||
| 199 | if (!String.IsNullOrEmpty(localizedControl.Text)) | ||
| 200 | { | ||
| 201 | symbol.Text = localizedControl.Text; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | private IVariableResolver CreateVariableResolver(IResolveContext context, IEnumerable<Localization> filteredLocalizations) | ||
| 209 | { | ||
| 210 | var variableResolver = this.ServiceProvider.GetService<IVariableResolver>(); | ||
| 211 | |||
| 212 | foreach (var localization in filteredLocalizations) | ||
| 213 | { | ||
| 214 | variableResolver.AddLocalization(localization); | ||
| 215 | } | ||
| 216 | |||
| 217 | // Gather all the wix variables. | ||
| 218 | var wixVariableSymbols = context.IntermediateRepresentation.Sections.SelectMany(s => s.Symbols).OfType<WixVariableSymbol>(); | ||
| 219 | foreach (var symbol in wixVariableSymbols) | ||
| 220 | { | ||
| 221 | variableResolver.AddVariable(symbol.SourceLineNumbers, symbol.Id.Id, symbol.Value, symbol.Overridable); | ||
| 222 | } | ||
| 223 | |||
| 224 | return variableResolver; | ||
| 225 | } | ||
| 226 | |||
| 227 | private bool TryGetCultureInfo(string culture, out CultureInfo cultureInfo) | ||
| 228 | { | ||
| 229 | cultureInfo = null; | ||
| 230 | |||
| 231 | if (!String.IsNullOrEmpty(culture)) | ||
| 232 | { | ||
| 233 | try | ||
| 234 | { | ||
| 235 | cultureInfo = new CultureInfo(culture, useUserOverride: false); | ||
| 236 | } | ||
| 237 | catch | ||
| 238 | { | ||
| 239 | this.Messaging.Write(""); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | return cultureInfo != null; | ||
| 244 | } | ||
| 245 | |||
| 246 | private static IEnumerable<Localization> FilterLocalizations(IResolveContext context) | ||
| 247 | { | ||
| 248 | var result = new List<Localization>(); | ||
| 249 | var filter = CalculateCultureFilter(context); | ||
| 250 | |||
| 251 | var localizations = context.Localizations.Concat(context.IntermediateRepresentation.Localizations).ToList(); | ||
| 252 | |||
| 253 | AddFilteredLocalizations(result, filter, localizations); | ||
| 254 | |||
| 255 | // Filter localizations provided by extensions with data. | ||
| 256 | var creator = context.ServiceProvider.GetService<ISymbolDefinitionCreator>(); | ||
| 257 | |||
| 258 | foreach (var data in context.ExtensionData) | ||
| 259 | { | ||
| 260 | var library = data.GetLibrary(creator); | ||
| 261 | |||
| 262 | if (library?.Localizations != null && library.Localizations.Any()) | ||
| 263 | { | ||
| 264 | var extensionFilter = (!filter.Any() && data.DefaultCulture != null) ? new[] { data.DefaultCulture } : filter; | ||
| 265 | |||
| 266 | AddFilteredLocalizations(result, extensionFilter, library.Localizations); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | return result; | ||
| 271 | } | ||
| 272 | |||
| 273 | private static IEnumerable<string> CalculateCultureFilter(IResolveContext context) | ||
| 274 | { | ||
| 275 | var filter = context.FilterCultures ?? Array.Empty<string>(); | ||
| 276 | |||
| 277 | // If no filter was specified, look for a language neutral localization file specified | ||
| 278 | // from the command-line (not embedded in the intermediate). If found, filter on language | ||
| 279 | // neutral. | ||
| 280 | if (!filter.Any() && context.Localizations.Any(l => String.IsNullOrEmpty(l.Culture))) | ||
| 281 | { | ||
| 282 | filter = new[] { String.Empty }; | ||
| 283 | } | ||
| 284 | |||
| 285 | return filter; | ||
| 286 | } | ||
| 287 | |||
| 288 | private static void AddFilteredLocalizations(List<Localization> result, IEnumerable<string> filter, IEnumerable<Localization> localizations) | ||
| 289 | { | ||
| 290 | // If there is no filter, return all localizations. | ||
| 291 | if (!filter.Any()) | ||
| 292 | { | ||
| 293 | result.AddRange(localizations); | ||
| 294 | } | ||
| 295 | else // filter localizations in order specified by the filter | ||
| 296 | { | ||
| 297 | foreach (var culture in filter) | ||
| 298 | { | ||
| 299 | result.AddRange(localizations.Where(l => culture.Equals(l.Culture, StringComparison.OrdinalIgnoreCase) || String.IsNullOrEmpty(l.Culture))); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
| 303 | } | ||
| 304 | } | ||
diff --git a/src/wix/WixToolset.Core/SourceFile.cs b/src/wix/WixToolset.Core/SourceFile.cs new file mode 100644 index 00000000..d7ea7a50 --- /dev/null +++ b/src/wix/WixToolset.Core/SourceFile.cs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | internal class SourceFile | ||
| 6 | { | ||
| 7 | public SourceFile(string sourcePath, string outputPath) | ||
| 8 | { | ||
| 9 | this.SourcePath = sourcePath; | ||
| 10 | this.OutputPath = outputPath; | ||
| 11 | } | ||
| 12 | |||
| 13 | public string OutputPath { get; } | ||
| 14 | |||
| 15 | public string SourcePath { get; } | ||
| 16 | } | ||
| 17 | } | ||
diff --git a/src/wix/WixToolset.Core/UnbindContext.cs b/src/wix/WixToolset.Core/UnbindContext.cs new file mode 100644 index 00000000..c3817a08 --- /dev/null +++ b/src/wix/WixToolset.Core/UnbindContext.cs | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility.Data; | ||
| 7 | |||
| 8 | internal class UnbindContext : IUnbindContext | ||
| 9 | { | ||
| 10 | internal UnbindContext(IServiceProvider serviceProvider) | ||
| 11 | { | ||
| 12 | this.ServiceProvider = serviceProvider; | ||
| 13 | } | ||
| 14 | |||
| 15 | public IServiceProvider ServiceProvider { get; } | ||
| 16 | |||
| 17 | public string ExportBasePath { get; set; } | ||
| 18 | |||
| 19 | public string InputFilePath { get; set; } | ||
| 20 | |||
| 21 | public string IntermediateFolder { get; set; } | ||
| 22 | |||
| 23 | public bool IsAdminImage { get; set; } | ||
| 24 | |||
| 25 | public bool SuppressExtractCabinets { get; set; } | ||
| 26 | |||
| 27 | public bool SuppressDemodularization { get; set; } | ||
| 28 | } | ||
| 29 | } | ||
diff --git a/src/wix/WixToolset.Core/Unbinder.cs b/src/wix/WixToolset.Core/Unbinder.cs new file mode 100644 index 00000000..3ef77083 --- /dev/null +++ b/src/wix/WixToolset.Core/Unbinder.cs | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Unbinder core of the WiX toolset. | ||
| 14 | /// </summary> | ||
| 15 | internal sealed class Unbinder : IUnbinder | ||
| 16 | { | ||
| 17 | public Unbinder(IServiceProvider serviceProvider) | ||
| 18 | { | ||
| 19 | this.ServiceProvider = serviceProvider; | ||
| 20 | |||
| 21 | var extensionManager = this.ServiceProvider.GetService<IExtensionManager>(); | ||
| 22 | this.BackendFactories = extensionManager.GetServices<IBackendFactory>(); | ||
| 23 | } | ||
| 24 | |||
| 25 | public IServiceProvider ServiceProvider { get; } | ||
| 26 | |||
| 27 | public IEnumerable<IBackendFactory> BackendFactories { get; } | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// Gets or sets whether the input msi is an admin image. | ||
| 31 | /// </summary> | ||
| 32 | /// <value>Set to true if the input msi is part of an admin image.</value> | ||
| 33 | public bool IsAdminImage { get; set; } | ||
| 34 | |||
| 35 | /// <summary> | ||
| 36 | /// Gets or sets the option to suppress demodularizing values. | ||
| 37 | /// </summary> | ||
| 38 | /// <value>The option to suppress demodularizing values.</value> | ||
| 39 | public bool SuppressDemodularization { get; set; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets or sets the option to suppress extracting cabinets. | ||
| 43 | /// </summary> | ||
| 44 | /// <value>The option to suppress extracting cabinets.</value> | ||
| 45 | public bool SuppressExtractCabinets { get; set; } | ||
| 46 | |||
| 47 | /// <summary> | ||
| 48 | /// Gets or sets the temporary path for the Binder. If left null, the binder | ||
| 49 | /// will use %TEMP% environment variable. | ||
| 50 | /// </summary> | ||
| 51 | /// <value>Path to temp files.</value> | ||
| 52 | public string TempFilesLocation => Path.GetTempPath(); | ||
| 53 | |||
| 54 | /// <summary> | ||
| 55 | /// Unbind a Windows Installer file. | ||
| 56 | /// </summary> | ||
| 57 | /// <param name="file">The Windows Installer file.</param> | ||
| 58 | /// <param name="outputType">The type of output to create.</param> | ||
| 59 | /// <param name="exportBasePath">The path where files should be exported.</param> | ||
| 60 | /// <returns>The output representing the database.</returns> | ||
| 61 | public Intermediate Unbind(string file, OutputType outputType, string exportBasePath) | ||
| 62 | { | ||
| 63 | if (!File.Exists(file)) | ||
| 64 | { | ||
| 65 | if (OutputType.Transform == outputType) | ||
| 66 | { | ||
| 67 | throw new WixException(ErrorMessages.FileNotFound(null, file, "Transform")); | ||
| 68 | } | ||
| 69 | else | ||
| 70 | { | ||
| 71 | throw new WixException(ErrorMessages.FileNotFound(null, file, "Database")); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | // if we don't have the temporary files object yet, get one | ||
| 76 | Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there | ||
| 77 | |||
| 78 | var context = new UnbindContext(this.ServiceProvider); | ||
| 79 | context.InputFilePath = file; | ||
| 80 | context.ExportBasePath = exportBasePath; | ||
| 81 | context.IntermediateFolder = this.TempFilesLocation; | ||
| 82 | context.IsAdminImage = this.IsAdminImage; | ||
| 83 | context.SuppressDemodularization = this.SuppressDemodularization; | ||
| 84 | context.SuppressExtractCabinets = this.SuppressExtractCabinets; | ||
| 85 | |||
| 86 | foreach (var factory in this.BackendFactories) | ||
| 87 | { | ||
| 88 | if (factory.TryCreateBackend(outputType.ToString(), file, out var backend)) | ||
| 89 | { | ||
| 90 | return backend.Unbind(context); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | // TODO: Display message that could not find a unbinder for output type? | ||
| 95 | |||
| 96 | return null; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
diff --git a/src/wix/WixToolset.Core/VariableResolution.cs b/src/wix/WixToolset.Core/VariableResolution.cs new file mode 100644 index 00000000..3b34e294 --- /dev/null +++ b/src/wix/WixToolset.Core/VariableResolution.cs | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Extensibility.Services; | ||
| 6 | |||
| 7 | internal class VariableResolution : IVariableResolution | ||
| 8 | { | ||
| 9 | /// <summary> | ||
| 10 | /// Indicates whether the variable should be delay resolved. | ||
| 11 | /// </summary> | ||
| 12 | public bool DelayedResolve { get; set; } | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Indicates whether the value is the default value of the variable. | ||
| 16 | /// </summary> | ||
| 17 | public bool IsDefault { get; set; } | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Indicates whether the value changed. | ||
| 21 | /// </summary> | ||
| 22 | public bool UpdatedValue { get; set; } | ||
| 23 | |||
| 24 | /// <summary> | ||
| 25 | /// Resolved value. | ||
| 26 | /// </summary> | ||
| 27 | public string Value { get; set; } | ||
| 28 | } | ||
| 29 | } \ No newline at end of file | ||
diff --git a/src/wix/WixToolset.Core/VariableResolver.cs b/src/wix/WixToolset.Core/VariableResolver.cs new file mode 100644 index 00000000..437cabb7 --- /dev/null +++ b/src/wix/WixToolset.Core/VariableResolver.cs | |||
| @@ -0,0 +1,197 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Text; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Bind; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// WiX variable resolver. | ||
| 14 | /// </summary> | ||
| 15 | internal class VariableResolver : IVariableResolver | ||
| 16 | { | ||
| 17 | private readonly Dictionary<string, BindVariable> locVariables; | ||
| 18 | private readonly Dictionary<string, BindVariable> wixVariables; | ||
| 19 | private readonly Dictionary<string, LocalizedControl> localizedControls; | ||
| 20 | |||
| 21 | /// <summary> | ||
| 22 | /// Instantiate a new VariableResolver. | ||
| 23 | /// </summary> | ||
| 24 | internal VariableResolver(IServiceProvider serviceProvider) | ||
| 25 | { | ||
| 26 | this.ServiceProvider = serviceProvider; | ||
| 27 | this.Messaging = serviceProvider.GetService<IMessaging>(); | ||
| 28 | |||
| 29 | this.locVariables = new Dictionary<string, BindVariable>(); | ||
| 30 | this.wixVariables = new Dictionary<string, BindVariable>(); | ||
| 31 | this.localizedControls = new Dictionary<string, LocalizedControl>(); | ||
| 32 | } | ||
| 33 | |||
| 34 | private IServiceProvider ServiceProvider { get; } | ||
| 35 | |||
| 36 | private IMessaging Messaging { get; } | ||
| 37 | |||
| 38 | public int VariableCount => this.wixVariables.Count; | ||
| 39 | |||
| 40 | public void AddLocalization(Localization localization) | ||
| 41 | { | ||
| 42 | foreach (var variable in localization.Variables) | ||
| 43 | { | ||
| 44 | if (!TryAddWixVariable(this.locVariables, variable)) | ||
| 45 | { | ||
| 46 | this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id)); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | foreach (KeyValuePair<string, LocalizedControl> localizedControl in localization.LocalizedControls) | ||
| 51 | { | ||
| 52 | if (!this.localizedControls.ContainsKey(localizedControl.Key)) | ||
| 53 | { | ||
| 54 | this.localizedControls.Add(localizedControl.Key, localizedControl.Value); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable) | ||
| 60 | { | ||
| 61 | var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber }; | ||
| 62 | |||
| 63 | if (!TryAddWixVariable(this.wixVariables, bindVariable)) | ||
| 64 | { | ||
| 65 | this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name)); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value) | ||
| 70 | { | ||
| 71 | return this.ResolveVariables(sourceLineNumbers, value, errorOnUnknown: true); | ||
| 72 | } | ||
| 73 | |||
| 74 | public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl) | ||
| 75 | { | ||
| 76 | var key = LocalizedControl.GetKey(dialog, control); | ||
| 77 | return this.localizedControls.TryGetValue(key, out localizedControl); | ||
| 78 | } | ||
| 79 | |||
| 80 | public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown) | ||
| 81 | { | ||
| 82 | var start = 0; | ||
| 83 | var defaulted = true; | ||
| 84 | var delayed = false; | ||
| 85 | var updated = false; | ||
| 86 | |||
| 87 | while (Common.TryParseWixVariable(value, start, out var parsed)) | ||
| 88 | { | ||
| 89 | var variableNamespace = parsed.Namespace; | ||
| 90 | var variableId = parsed.Name; | ||
| 91 | var variableDefaultValue = parsed.DefaultValue; | ||
| 92 | |||
| 93 | // check for an escape sequence of !! indicating the match is not a variable expression | ||
| 94 | if (0 < parsed.Index && '!' == value[parsed.Index - 1]) | ||
| 95 | { | ||
| 96 | var sb = new StringBuilder(value); | ||
| 97 | sb.Remove(parsed.Index - 1, 1); | ||
| 98 | value = sb.ToString(); | ||
| 99 | |||
| 100 | updated = true; | ||
| 101 | start = parsed.Index + parsed.Length - 1; | ||
| 102 | |||
| 103 | continue; | ||
| 104 | } | ||
| 105 | |||
| 106 | string resolvedValue = null; | ||
| 107 | |||
| 108 | if ("loc" == variableNamespace) | ||
| 109 | { | ||
| 110 | // localization variables do not support inline default values | ||
| 111 | if (variableDefaultValue != null) | ||
| 112 | { | ||
| 113 | this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue)); | ||
| 114 | continue; | ||
| 115 | } | ||
| 116 | |||
| 117 | if (this.locVariables.TryGetValue(variableId, out var bindVariable)) | ||
| 118 | { | ||
| 119 | resolvedValue = bindVariable.Value; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | else if ("wix" == variableNamespace) | ||
| 123 | { | ||
| 124 | if (this.wixVariables.TryGetValue(variableId, out var bindVariable)) | ||
| 125 | { | ||
| 126 | resolvedValue = bindVariable.Value ?? String.Empty; | ||
| 127 | defaulted = false; | ||
| 128 | } | ||
| 129 | else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified | ||
| 130 | { | ||
| 131 | resolvedValue = variableDefaultValue; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | if ("bind" == variableNamespace) | ||
| 136 | { | ||
| 137 | // Can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort. | ||
| 138 | delayed = true; | ||
| 139 | start = parsed.Index + parsed.Length - 1; | ||
| 140 | } | ||
| 141 | else | ||
| 142 | { | ||
| 143 | // insert the resolved value if it was found or display an error | ||
| 144 | if (null != resolvedValue) | ||
| 145 | { | ||
| 146 | if (parsed.Index == 0 && parsed.Length == value.Length) | ||
| 147 | { | ||
| 148 | value = resolvedValue; | ||
| 149 | } | ||
| 150 | else | ||
| 151 | { | ||
| 152 | var sb = new StringBuilder(value); | ||
| 153 | sb.Remove(parsed.Index, parsed.Length); | ||
| 154 | sb.Insert(parsed.Index, resolvedValue); | ||
| 155 | value = sb.ToString(); | ||
| 156 | } | ||
| 157 | |||
| 158 | updated = true; | ||
| 159 | start = parsed.Index; | ||
| 160 | } | ||
| 161 | else | ||
| 162 | { | ||
| 163 | if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable | ||
| 164 | { | ||
| 165 | this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId)); | ||
| 166 | } | ||
| 167 | else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable | ||
| 168 | { | ||
| 169 | this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId)); | ||
| 170 | } | ||
| 171 | |||
| 172 | start = parsed.Index + parsed.Length; | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | return new VariableResolution | ||
| 178 | { | ||
| 179 | DelayedResolve = delayed, | ||
| 180 | IsDefault = defaulted, | ||
| 181 | UpdatedValue = updated, | ||
| 182 | Value = value, | ||
| 183 | }; | ||
| 184 | } | ||
| 185 | |||
| 186 | private static bool TryAddWixVariable(IDictionary<string, BindVariable> variables, BindVariable variable) | ||
| 187 | { | ||
| 188 | if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable)) | ||
| 189 | { | ||
| 190 | variables[variable.Id] = variable; | ||
| 191 | return true; | ||
| 192 | } | ||
| 193 | |||
| 194 | return variable.Overridable; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | } | ||
diff --git a/src/wix/WixToolset.Core/WixToolset.Core.csproj b/src/wix/WixToolset.Core/WixToolset.Core.csproj new file mode 100644 index 00000000..7242d500 --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolset.Core.csproj | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFrameworks>netstandard2.0</TargetFrameworks> | ||
| 7 | <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks> | ||
| 8 | <Description>Core</Description> | ||
| 9 | <Title>WiX Toolset Core</Title> | ||
| 10 | <DebugType>embedded</DebugType> | ||
| 11 | <PublishRepositoryUrl>true</PublishRepositoryUrl> | ||
| 12 | <NBGV_EmitThisAssemblyClass>true</NBGV_EmitThisAssemblyClass> | ||
| 13 | <CreateDocumentationFile>true</CreateDocumentationFile> | ||
| 14 | </PropertyGroup> | ||
| 15 | |||
| 16 | <ItemGroup> | ||
| 17 | <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> | ||
| 18 | <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1> | ||
| 19 | </AssemblyAttribute> | ||
| 20 | <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> | ||
| 21 | <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1> | ||
| 22 | </AssemblyAttribute> | ||
| 23 | <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> | ||
| 24 | <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1> | ||
| 25 | </AssemblyAttribute> | ||
| 26 | </ItemGroup> | ||
| 27 | |||
| 28 | <ItemGroup> | ||
| 29 | <PackageReference Include="WixToolset.Data" Version="4.0.*" /> | ||
| 30 | <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" /> | ||
| 31 | <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" /> | ||
| 32 | </ItemGroup> | ||
| 33 | |||
| 34 | <!-- | ||
| 35 | These package references are duplicated in WixToolset.Core.TestPackage.csproj. If | ||
| 36 | you update these here, be sure to update them there. | ||
| 37 | --> | ||
| 38 | <ItemGroup> | ||
| 39 | <PackageReference Include="System.Text.Encoding.CodePages" Version="4.6.0" /> | ||
| 40 | <PackageReference Include="NuGet.Versioning" Version="5.6.0" /> | ||
| 41 | </ItemGroup> | ||
| 42 | |||
| 43 | <ItemGroup> | ||
| 44 | <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | ||
| 45 | <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" /> | ||
| 46 | </ItemGroup> | ||
| 47 | </Project> | ||
diff --git a/src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject b/src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject new file mode 100644 index 00000000..c6001ebe --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <ProjectConfiguration> | ||
| 2 | <Settings> | ||
| 3 | <AdditionalFilesToIncludeForProject> | ||
| 4 | <Value>..\..\version.json</Value> | ||
| 5 | </AdditionalFilesToIncludeForProject> | ||
| 6 | </Settings> | ||
| 7 | </ProjectConfiguration> \ No newline at end of file | ||
diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs new file mode 100644 index 00000000..5d700ba0 --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs | |||
| @@ -0,0 +1,117 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Core.CommandLine; | ||
| 8 | using WixToolset.Core.ExtensibilityServices; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Extensibility.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | |||
| 13 | internal class WixToolsetServiceProvider : IWixToolsetCoreServiceProvider | ||
| 14 | { | ||
| 15 | public WixToolsetServiceProvider() | ||
| 16 | { | ||
| 17 | this.CreationFunctions = new Dictionary<Type, Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, object>>(); | ||
| 18 | this.Singletons = new Dictionary<Type, object>(); | ||
| 19 | |||
| 20 | // Singletons. | ||
| 21 | this.AddService((provider, singletons) => AddSingleton<IExtensionManager>(singletons, new ExtensionManager(provider))); | ||
| 22 | this.AddService((provider, singletons) => AddSingleton<IMessaging>(singletons, new Messaging())); | ||
| 23 | this.AddService((provider, singletons) => AddSingleton<ISymbolDefinitionCreator>(singletons, new SymbolDefinitionCreator(provider))); | ||
| 24 | this.AddService((provider, singletons) => AddSingleton<IParseHelper>(singletons, new ParseHelper(provider))); | ||
| 25 | this.AddService((provider, singletons) => AddSingleton<IPreprocessHelper>(singletons, new PreprocessHelper(provider))); | ||
| 26 | this.AddService((provider, singletons) => AddSingleton<IBackendHelper>(singletons, new BackendHelper(provider))); | ||
| 27 | this.AddService((provider, singletons) => AddSingleton<IPathResolver>(singletons, new PathResolver())); | ||
| 28 | this.AddService((provider, singletons) => AddSingleton<IWixBranding>(singletons, new WixBranding())); | ||
| 29 | |||
| 30 | // Transients. | ||
| 31 | this.AddService<ICommandLineArguments>((provider, singletons) => new CommandLineArguments(provider)); | ||
| 32 | this.AddService<ICommandLineContext>((provider, singletons) => new CommandLineContext(provider)); | ||
| 33 | this.AddService<ICommandLine>((provider, singletons) => new CommandLine.CommandLine(provider)); | ||
| 34 | this.AddService<IPreprocessContext>((provider, singletons) => new PreprocessContext(provider)); | ||
| 35 | this.AddService<ICompileContext>((provider, singletons) => new CompileContext(provider)); | ||
| 36 | this.AddService<ILibraryContext>((provider, singletons) => new LibraryContext(provider)); | ||
| 37 | this.AddService<ILinkContext>((provider, singletons) => new LinkContext(provider)); | ||
| 38 | this.AddService<IResolveContext>((provider, singletons) => new ResolveContext(provider)); | ||
| 39 | this.AddService<IBindContext>((provider, singletons) => new BindContext(provider)); | ||
| 40 | this.AddService<IDecompileContext>((provider, singletons) => new DecompileContext(provider)); | ||
| 41 | this.AddService<ILayoutContext>((provider, singletons) => new LayoutContext(provider)); | ||
| 42 | this.AddService<IInscribeContext>((provider, singletons) => new InscribeContext(provider)); | ||
| 43 | this.AddService<IUnbindContext>((provider, singletons) => new UnbindContext(provider)); | ||
| 44 | |||
| 45 | this.AddService<IBindFileWithPath>((provider, singletons) => new BindFileWithPath()); | ||
| 46 | this.AddService<IBindPath>((provider, singletons) => new BindPath()); | ||
| 47 | this.AddService<IBindResult>((provider, singletons) => new BindResult()); | ||
| 48 | this.AddService<IComponentKeyPath>((provider, singletons) => new ComponentKeyPath()); | ||
| 49 | this.AddService<IDecompileResult>((provider, singletons) => new DecompileResult()); | ||
| 50 | this.AddService<IIncludedFile>((provider, singletons) => new IncludedFile()); | ||
| 51 | this.AddService<IPreprocessResult>((provider, singletons) => new PreprocessResult()); | ||
| 52 | this.AddService<IResolvedDirectory>((provider, singletons) => new ResolvedDirectory()); | ||
| 53 | this.AddService<IResolveFileResult>((provider, singletons) => new ResolveFileResult()); | ||
| 54 | this.AddService<IResolveResult>((provider, singletons) => new ResolveResult()); | ||
| 55 | this.AddService<IResolvedCabinet>((provider, singletons) => new ResolvedCabinet()); | ||
| 56 | this.AddService<IVariableResolution>((provider, singletons) => new VariableResolution()); | ||
| 57 | |||
| 58 | this.AddService<IBinder>((provider, singletons) => new Binder(provider)); | ||
| 59 | this.AddService<ICompiler>((provider, singletons) => new Compiler(provider)); | ||
| 60 | this.AddService<IDecompiler>((provider, singletons) => new Decompiler(provider)); | ||
| 61 | this.AddService<ILayoutCreator>((provider, singletons) => new LayoutCreator(provider)); | ||
| 62 | this.AddService<IPreprocessor>((provider, singletons) => new Preprocessor(provider)); | ||
| 63 | this.AddService<ILibrarian>((provider, singletons) => new Librarian(provider)); | ||
| 64 | this.AddService<ILinker>((provider, singletons) => new Linker(provider)); | ||
| 65 | this.AddService<IResolver>((provider, singletons) => new Resolver(provider)); | ||
| 66 | this.AddService<IUnbinder>((provider, singletons) => new Unbinder(provider)); | ||
| 67 | |||
| 68 | this.AddService<ILocalizationParser>((provider, singletons) => new LocalizationParser(provider)); | ||
| 69 | this.AddService<IVariableResolver>((provider, singletons) => new VariableResolver(provider)); | ||
| 70 | } | ||
| 71 | |||
| 72 | private Dictionary<Type, Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, object>> CreationFunctions { get; } | ||
| 73 | |||
| 74 | private Dictionary<Type, object> Singletons { get; } | ||
| 75 | |||
| 76 | public object GetService(Type serviceType) | ||
| 77 | { | ||
| 78 | if (serviceType == null) | ||
| 79 | { | ||
| 80 | throw new ArgumentNullException(nameof(serviceType)); | ||
| 81 | } | ||
| 82 | |||
| 83 | if (!this.Singletons.TryGetValue(serviceType, out var service)) | ||
| 84 | { | ||
| 85 | if (this.CreationFunctions.TryGetValue(serviceType, out var creationFunction)) | ||
| 86 | { | ||
| 87 | service = creationFunction(this, this.Singletons); | ||
| 88 | |||
| 89 | #if DEBUG | ||
| 90 | if (!serviceType.IsAssignableFrom(service?.GetType())) | ||
| 91 | { | ||
| 92 | throw new InvalidOperationException($"Creation function for service type: {serviceType.Name} created incompatible service with type: {service?.GetType()}"); | ||
| 93 | } | ||
| 94 | #endif | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | return service; | ||
| 99 | } | ||
| 100 | |||
| 101 | public void AddService(Type serviceType, Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, object> creationFunction) | ||
| 102 | { | ||
| 103 | this.CreationFunctions[serviceType] = creationFunction; | ||
| 104 | } | ||
| 105 | |||
| 106 | public void AddService<T>(Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, T> creationFunction) where T : class | ||
| 107 | { | ||
| 108 | this.AddService(typeof(T), creationFunction); | ||
| 109 | } | ||
| 110 | |||
| 111 | private static T AddSingleton<T>(Dictionary<Type, object> singletons, T service) where T : class | ||
| 112 | { | ||
| 113 | singletons.Add(typeof(T), service); | ||
| 114 | return service; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs b/src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs new file mode 100644 index 00000000..8e07070b --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolset.Core | ||
| 4 | { | ||
| 5 | using WixToolset.Extensibility.Services; | ||
| 6 | |||
| 7 | /// <summary> | ||
| 8 | /// Class for creating <see cref="IWixToolsetCoreServiceProvider"/>. | ||
| 9 | /// </summary> | ||
| 10 | public static class WixToolsetServiceProviderFactory | ||
| 11 | { | ||
| 12 | /// <summary> | ||
| 13 | /// Creates a new <see cref="IWixToolsetCoreServiceProvider"/>. | ||
| 14 | /// </summary> | ||
| 15 | /// <returns>The created <see cref="IWixToolsetCoreServiceProvider"/></returns> | ||
| 16 | public static IWixToolsetCoreServiceProvider CreateServiceProvider() | ||
| 17 | { | ||
| 18 | return new WixToolsetServiceProvider(); | ||
| 19 | } | ||
| 20 | } | ||
| 21 | } | ||
diff --git a/src/wix/appveyor.cmd b/src/wix/appveyor.cmd new file mode 100644 index 00000000..02db695b --- /dev/null +++ b/src/wix/appveyor.cmd | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | @setlocal | ||
| 2 | @pushd %~dp0 | ||
| 3 | @set _P=%~dp0build\Release\publish | ||
| 4 | @set _C=Release | ||
| 5 | @if /i "%1"=="debug" set _C=Debug | ||
| 6 | |||
| 7 | :: Restore | ||
| 8 | msbuild -p:Configuration=%_C% -t:Restore || exit /b | ||
| 9 | |||
| 10 | :: Build | ||
| 11 | msbuild -p:Configuration=%_C% || exit /b | ||
| 12 | |||
| 13 | :: Test | ||
| 14 | dotnet test -c %_C% --no-build || exit /b | ||
| 15 | |||
| 16 | :: Pack | ||
| 17 | msbuild -p:Configuration=%_C% -p:NoBuild=true -t:Pack || exit /b | ||
| 18 | |||
| 19 | @popd | ||
| 20 | @endlocal | ||
diff --git a/src/wix/appveyor.yml b/src/wix/appveyor.yml new file mode 100644 index 00000000..364569cf --- /dev/null +++ b/src/wix/appveyor.yml | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | # 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. | ||
| 2 | # | ||
| 3 | # Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml | ||
| 4 | # then update all of the repos. | ||
| 5 | |||
| 6 | branches: | ||
| 7 | only: | ||
| 8 | - master | ||
| 9 | - develop | ||
| 10 | |||
| 11 | image: Visual Studio 2019 | ||
| 12 | |||
| 13 | version: 0.0.0.{build} | ||
| 14 | configuration: Release | ||
| 15 | |||
| 16 | environment: | ||
| 17 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true | ||
| 18 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 | ||
| 19 | NUGET_XMLDOC_MODE: skip | ||
| 20 | |||
| 21 | build_script: | ||
| 22 | - appveyor.cmd | ||
| 23 | |||
| 24 | test: off | ||
| 25 | |||
| 26 | pull_requests: | ||
| 27 | do_not_increment_build_number: true | ||
| 28 | |||
| 29 | nuget: | ||
| 30 | disable_publish_on_pr: true | ||
| 31 | |||
| 32 | skip_branch_with_pr: true | ||
| 33 | skip_tags: true | ||
| 34 | |||
| 35 | artifacts: | ||
| 36 | - path: build\Release\**\*.nupkg | ||
| 37 | name: nuget | ||
| 38 | - path: build\Release\**\*.snupkg | ||
| 39 | name: snupkg | ||
| 40 | |||
| 41 | notifications: | ||
| 42 | - provider: Slack | ||
| 43 | incoming_webhook: | ||
| 44 | secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA= | ||
diff --git a/src/wix/nuget.config b/src/wix/nuget.config new file mode 100644 index 00000000..022f9240 --- /dev/null +++ b/src/wix/nuget.config | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <configuration> | ||
| 3 | <packageSources> | ||
| 4 | <clear /> | ||
| 5 | <add key="wixtoolset-burn" value="https://ci.appveyor.com/nuget/wixtoolset-burn" /> | ||
| 6 | <add key="wixtoolset-core-native" value="https://ci.appveyor.com/nuget/wixtoolset-core-native" /> | ||
| 7 | <add key="wixtoolset-data" value="https://ci.appveyor.com/nuget/wixtoolset-data" /> | ||
| 8 | <add key="wixtoolset-dtf" value="https://ci.appveyor.com/nuget/wixtoolset-dtf" /> | ||
| 9 | <add key="wixtoolset-extensibility" value="https://ci.appveyor.com/nuget/wixtoolset-extensibility" /> | ||
| 10 | <add key="wixbuildtools" value="https://ci.appveyor.com/nuget/wixbuildtools" /> | ||
| 11 | <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> | ||
| 12 | </packageSources> | ||
| 13 | </configuration> \ No newline at end of file | ||
diff --git a/src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj b/src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj new file mode 100644 index 00000000..88210bd4 --- /dev/null +++ b/src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFramework>netcoreapp3.1</TargetFramework> | ||
| 7 | <IsPackable>false</IsPackable> | ||
| 8 | <OutputType>Exe</OutputType> | ||
| 9 | </PropertyGroup> | ||
| 10 | |||
| 11 | <ItemGroup> | ||
| 12 | <ProjectReference Include="..\..\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" /> | ||
| 13 | </ItemGroup> | ||
| 14 | |||
| 15 | <ItemGroup> | ||
| 16 | <ExtensionWxs Include="..\Example.Extension\Data\example.wxs"> | ||
| 17 | <WixlibPath>$(BaseOutputPath)TestData\$(Configuration)\example.wixlib</WixlibPath> | ||
| 18 | </ExtensionWxs> | ||
| 19 | </ItemGroup> | ||
| 20 | |||
| 21 | <Target Name="BuildExtensionWixlibs" | ||
| 22 | AfterTargets="AfterBuild" | ||
| 23 | Inputs="@(ExtensionWxs)" | ||
| 24 | Outputs="%(ExtensionWxs.WixlibPath)" | ||
| 25 | Condition=" '$(NCrunch)'!='1' "> | ||
| 26 | |||
| 27 | <Exec Command="dotnet @(TargetPathWithTargetPlatformMoniker) "$(IntermediateOutputPath) " "%(ExtensionWxs.WixlibPath)" "%(ExtensionWxs.Filename)%(ExtensionWxs.Extension)"" | ||
| 28 | WorkingDirectory="%(ExtensionWxs.RelativeDir)" /> | ||
| 29 | |||
| 30 | <Message Importance="high" Text="@(ExtensionWxs) -> %(ExtensionWxs.WixlibPath)" /> | ||
| 31 | </Target> | ||
| 32 | </Project> | ||
diff --git a/src/wix/test/CompileCoreTestExtensionWixlib/Program.cs b/src/wix/test/CompileCoreTestExtensionWixlib/Program.cs new file mode 100644 index 00000000..323b5e5e --- /dev/null +++ b/src/wix/test/CompileCoreTestExtensionWixlib/Program.cs | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | using System.Collections.Generic; | ||
| 4 | using WixToolset.Core.TestPackage; | ||
| 5 | |||
| 6 | namespace CompileCoreTestExtensionWixlib | ||
| 7 | { | ||
| 8 | // We want to be able to test Core with extensions, but there's no easy way to build an extension without Tools. | ||
| 9 | // So we have this helper exe. | ||
| 10 | public class Program | ||
| 11 | { | ||
| 12 | public static void Main(string[] args) | ||
| 13 | { | ||
| 14 | var intermediateFolder = args[0]; | ||
| 15 | var wixlibPath = args[1]; | ||
| 16 | |||
| 17 | var buildArgs = new List<string>(); | ||
| 18 | buildArgs.Add("build"); | ||
| 19 | buildArgs.Add("-bindfiles"); | ||
| 20 | buildArgs.Add("-bindpath"); | ||
| 21 | buildArgs.Add("Data"); | ||
| 22 | buildArgs.Add("-intermediateFolder"); | ||
| 23 | buildArgs.Add(intermediateFolder); | ||
| 24 | buildArgs.Add("-o"); | ||
| 25 | buildArgs.Add(wixlibPath); | ||
| 26 | |||
| 27 | foreach (var path in args[2].Split(';')) | ||
| 28 | { | ||
| 29 | buildArgs.Add(path); | ||
| 30 | } | ||
| 31 | |||
| 32 | var result = WixRunner.Execute(buildArgs.ToArray()); | ||
| 33 | |||
| 34 | result.AssertSuccess(); | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/src/wix/test/Example.Extension/Data/example.txt b/src/wix/test/Example.Extension/Data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/Example.Extension/Data/example.txt | |||
| @@ -0,0 +1 @@ | |||
| This is example.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/Example.Extension/Data/example.wxs b/src/wix/test/Example.Extension/Data/example.wxs new file mode 100644 index 00000000..af5d5086 --- /dev/null +++ b/src/wix/test/Example.Extension/Data/example.wxs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <Property Id="PropertyFromExampleWir" Value="FromWir" /> | ||
| 4 | |||
| 5 | <Binary Id="BinFromWir" SourceFile="example.txt" /> | ||
| 6 | </Fragment> | ||
| 7 | <Fragment> | ||
| 8 | <BootstrapperApplication Id="fakeba"> | ||
| 9 | <BootstrapperApplicationDll SourceFile="example.txt" /> | ||
| 10 | </BootstrapperApplication> | ||
| 11 | </Fragment> | ||
| 12 | <Fragment> | ||
| 13 | <BundleExtension Id="ExampleBundleExtension" SourceFile="example.txt" /> | ||
| 14 | </Fragment> | ||
| 15 | </Wix> | ||
diff --git a/src/wix/test/Example.Extension/Example.Extension.csproj b/src/wix/test/Example.Extension/Example.Extension.csproj new file mode 100644 index 00000000..9be10d35 --- /dev/null +++ b/src/wix/test/Example.Extension/Example.Extension.csproj | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFramework>netcoreapp3.1</TargetFramework> | ||
| 7 | <IsPackable>false</IsPackable> | ||
| 8 | <DebugType>embedded</DebugType> | ||
| 9 | </PropertyGroup> | ||
| 10 | |||
| 11 | <ItemGroup> | ||
| 12 | <!-- This .wixlib is built by CompileCoreTestExtensionWixlib.csproj --> | ||
| 13 | <EmbeddedResource Include="$(BaseOutputPath)TestData\$(Configuration)\Example.wixlib" /> | ||
| 14 | </ItemGroup> | ||
| 15 | |||
| 16 | <ItemGroup> | ||
| 17 | <ProjectReference Include="..\CompileCoreTestExtensionWixlib\CompileCoreTestExtensionWixlib.csproj" /> | ||
| 18 | </ItemGroup> | ||
| 19 | |||
| 20 | <ItemGroup> | ||
| 21 | <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" /> | ||
| 22 | </ItemGroup> | ||
| 23 | |||
| 24 | </Project> | ||
diff --git a/src/wix/test/Example.Extension/ExampleCompilerExtension.cs b/src/wix/test/Example.Extension/ExampleCompilerExtension.cs new file mode 100644 index 00000000..5b8d4b3f --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleCompilerExtension.cs | |||
| @@ -0,0 +1,195 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Xml.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Extensibility; | ||
| 10 | |||
| 11 | internal class ExampleCompilerExtension : BaseCompilerExtension | ||
| 12 | { | ||
| 13 | public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; | ||
| 14 | public string BundleExtensionId => "ExampleBundleExtension"; | ||
| 15 | |||
| 16 | public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context) | ||
| 17 | { | ||
| 18 | var processed = false; | ||
| 19 | |||
| 20 | switch (parentElement.Name.LocalName) | ||
| 21 | { | ||
| 22 | case "Bundle": | ||
| 23 | case "Fragment": | ||
| 24 | switch (element.Name.LocalName) | ||
| 25 | { | ||
| 26 | case "ExampleEnsureTable": | ||
| 27 | this.ParseExampleEnsureTableElement(intermediate, section, element); | ||
| 28 | processed = true; | ||
| 29 | break; | ||
| 30 | case "ExampleSearch": | ||
| 31 | this.ParseExampleSearchElement(intermediate, section, element); | ||
| 32 | processed = true; | ||
| 33 | break; | ||
| 34 | case "ExampleSearchRef": | ||
| 35 | this.ParseExampleSearchRefElement(intermediate, section, element); | ||
| 36 | processed = true; | ||
| 37 | break; | ||
| 38 | } | ||
| 39 | break; | ||
| 40 | case "Component": | ||
| 41 | switch (element.Name.LocalName) | ||
| 42 | { | ||
| 43 | case "Example": | ||
| 44 | this.ParseExampleElement(intermediate, section, element); | ||
| 45 | processed = true; | ||
| 46 | break; | ||
| 47 | } | ||
| 48 | break; | ||
| 49 | } | ||
| 50 | |||
| 51 | if (!processed) | ||
| 52 | { | ||
| 53 | base.ParseElement(intermediate, section, parentElement, element, context); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | private void ParseExampleElement(Intermediate intermediate, IntermediateSection section, XElement element) | ||
| 58 | { | ||
| 59 | var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); | ||
| 60 | Identifier id = null; | ||
| 61 | string value = null; | ||
| 62 | |||
| 63 | foreach (var attrib in element.Attributes()) | ||
| 64 | { | ||
| 65 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) | ||
| 66 | { | ||
| 67 | switch (attrib.Name.LocalName) | ||
| 68 | { | ||
| 69 | case "Id": | ||
| 70 | id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 71 | break; | ||
| 72 | |||
| 73 | case "Value": | ||
| 74 | value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 75 | break; | ||
| 76 | |||
| 77 | default: | ||
| 78 | this.ParseHelper.UnexpectedAttribute(element, attrib); | ||
| 79 | break; | ||
| 80 | } | ||
| 81 | } | ||
| 82 | else | ||
| 83 | { | ||
| 84 | this.ParseAttribute(intermediate, section, element, attrib, null); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | if (null == id) | ||
| 89 | { | ||
| 90 | //this.Messaging(WixErrors.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); | ||
| 91 | } | ||
| 92 | |||
| 93 | if (!this.Messaging.EncounteredError) | ||
| 94 | { | ||
| 95 | var symbol = this.ParseHelper.CreateSymbol(section, sourceLineNumbers, "Example", id); | ||
| 96 | symbol.Set(0, value); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | private void ParseExampleEnsureTableElement(Intermediate intermediate, IntermediateSection section, XElement element) | ||
| 101 | { | ||
| 102 | var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); | ||
| 103 | this.ParseHelper.EnsureTable(section, sourceLineNumbers, ExampleTableDefinitions.NotInAll); | ||
| 104 | } | ||
| 105 | |||
| 106 | private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element) | ||
| 107 | { | ||
| 108 | var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); | ||
| 109 | Identifier id = null; | ||
| 110 | string searchFor = null; | ||
| 111 | string variable = null; | ||
| 112 | string condition = null; | ||
| 113 | string after = null; | ||
| 114 | |||
| 115 | foreach (var attrib in element.Attributes()) | ||
| 116 | { | ||
| 117 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) | ||
| 118 | { | ||
| 119 | switch (attrib.Name.LocalName) | ||
| 120 | { | ||
| 121 | case "Id": | ||
| 122 | id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 123 | break; | ||
| 124 | case "Variable": | ||
| 125 | variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 126 | break; | ||
| 127 | case "Condition": | ||
| 128 | condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 129 | break; | ||
| 130 | case "After": | ||
| 131 | after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 132 | break; | ||
| 133 | case "SearchFor": | ||
| 134 | searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 135 | break; | ||
| 136 | |||
| 137 | default: | ||
| 138 | this.ParseHelper.UnexpectedAttribute(element, attrib); | ||
| 139 | break; | ||
| 140 | } | ||
| 141 | } | ||
| 142 | else | ||
| 143 | { | ||
| 144 | this.ParseAttribute(intermediate, section, element, attrib, null); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | if (null == id) | ||
| 149 | { | ||
| 150 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); | ||
| 151 | } | ||
| 152 | |||
| 153 | if (!this.Messaging.EncounteredError) | ||
| 154 | { | ||
| 155 | this.ParseHelper.CreateWixSearchSymbol(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId); | ||
| 156 | } | ||
| 157 | |||
| 158 | if (!this.Messaging.EncounteredError) | ||
| 159 | { | ||
| 160 | var symbol = section.AddSymbol(new ExampleSearchSymbol(sourceLineNumbers, id) | ||
| 161 | { | ||
| 162 | SearchFor = searchFor, | ||
| 163 | }); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element) | ||
| 168 | { | ||
| 169 | var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); | ||
| 170 | |||
| 171 | foreach (var attrib in element.Attributes()) | ||
| 172 | { | ||
| 173 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) | ||
| 174 | { | ||
| 175 | switch (attrib.Name.LocalName) | ||
| 176 | { | ||
| 177 | case "Id": | ||
| 178 | var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 179 | this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, ExampleSymbolDefinitions.ExampleSearch, refId); | ||
| 180 | break; | ||
| 181 | default: | ||
| 182 | this.ParseHelper.UnexpectedAttribute(element, attrib); | ||
| 183 | break; | ||
| 184 | } | ||
| 185 | } | ||
| 186 | else | ||
| 187 | { | ||
| 188 | this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
diff --git a/src/wix/test/Example.Extension/ExampleExtensionData.cs b/src/wix/test/Example.Extension/ExampleExtensionData.cs new file mode 100644 index 00000000..91d60eb9 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleExtensionData.cs | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Extensibility; | ||
| 7 | |||
| 8 | internal class ExampleExtensionData : IExtensionData | ||
| 9 | { | ||
| 10 | public string DefaultCulture => null; | ||
| 11 | |||
| 12 | public Intermediate GetLibrary(ISymbolDefinitionCreator symbolDefinitions) | ||
| 13 | { | ||
| 14 | return Intermediate.Load(typeof(ExampleExtensionData).Assembly, "Example.Extension.Example.wixlib", symbolDefinitions); | ||
| 15 | } | ||
| 16 | |||
| 17 | public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) | ||
| 18 | { | ||
| 19 | symbolDefinition = ExampleSymbolDefinitions.ByName(name); | ||
| 20 | return symbolDefinition != null; | ||
| 21 | } | ||
| 22 | } | ||
| 23 | } \ No newline at end of file | ||
diff --git a/src/wix/test/Example.Extension/ExampleExtensionFactory.cs b/src/wix/test/Example.Extension/ExampleExtensionFactory.cs new file mode 100644 index 00000000..e54561ee --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleExtensionFactory.cs | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Extensibility; | ||
| 7 | using WixToolset.Extensibility.Services; | ||
| 8 | |||
| 9 | public class ExampleExtensionFactory : IExtensionFactory | ||
| 10 | { | ||
| 11 | private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension; | ||
| 12 | |||
| 13 | public ExampleExtensionFactory(IWixToolsetCoreServiceProvider serviceProvider) | ||
| 14 | { | ||
| 15 | this.ServiceProvider = serviceProvider; | ||
| 16 | } | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// This exists just to show it is possible to get a service provider to the extension factory. | ||
| 20 | /// </summary> | ||
| 21 | private IWixToolsetCoreServiceProvider ServiceProvider { get; } | ||
| 22 | |||
| 23 | public bool TryCreateExtension(Type extensionType, out object extension) | ||
| 24 | { | ||
| 25 | if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension)) | ||
| 26 | { | ||
| 27 | if (this.preprocessorExtension == null) | ||
| 28 | { | ||
| 29 | this.preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine(); | ||
| 30 | } | ||
| 31 | |||
| 32 | extension = this.preprocessorExtension; | ||
| 33 | } | ||
| 34 | else if (extensionType == typeof(ICompilerExtension)) | ||
| 35 | { | ||
| 36 | extension = new ExampleCompilerExtension(); | ||
| 37 | } | ||
| 38 | else if (extensionType == typeof(IExtensionData)) | ||
| 39 | { | ||
| 40 | extension = new ExampleExtensionData(); | ||
| 41 | } | ||
| 42 | else if (extensionType == typeof(IWindowsInstallerBackendBinderExtension)) | ||
| 43 | { | ||
| 44 | extension = new ExampleWindowsInstallerBackendExtension(); | ||
| 45 | } | ||
| 46 | else | ||
| 47 | { | ||
| 48 | extension = null; | ||
| 49 | } | ||
| 50 | |||
| 51 | return extension != null; | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
diff --git a/src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs b/src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs new file mode 100644 index 00000000..7244798a --- /dev/null +++ b/src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Extensibility; | ||
| 8 | using WixToolset.Extensibility.Data; | ||
| 9 | using WixToolset.Extensibility.Services; | ||
| 10 | |||
| 11 | internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine | ||
| 12 | { | ||
| 13 | private string exampleValueFromCommandLine; | ||
| 14 | |||
| 15 | public IReadOnlyCollection<ExtensionCommandLineSwitch> CommandLineSwitches => throw new NotImplementedException(); | ||
| 16 | |||
| 17 | public ExamplePreprocessorExtensionAndCommandLine() | ||
| 18 | { | ||
| 19 | this.Prefixes = new[] { "ex" }; | ||
| 20 | } | ||
| 21 | |||
| 22 | public void PreParse(ICommandLineContext context) | ||
| 23 | { | ||
| 24 | } | ||
| 25 | |||
| 26 | public bool TryParseArgument(ICommandLineParser parser, string argument) | ||
| 27 | { | ||
| 28 | if (parser.IsSwitch(argument) && argument.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase)) | ||
| 29 | { | ||
| 30 | this.exampleValueFromCommandLine = parser.GetNextArgumentOrError(argument); | ||
| 31 | return true; | ||
| 32 | } | ||
| 33 | |||
| 34 | return false; | ||
| 35 | } | ||
| 36 | |||
| 37 | public bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) | ||
| 38 | { | ||
| 39 | command = null; | ||
| 40 | return false; | ||
| 41 | } | ||
| 42 | |||
| 43 | public void PostParse() | ||
| 44 | { | ||
| 45 | } | ||
| 46 | |||
| 47 | public override string GetVariableValue(string prefix, string name) | ||
| 48 | { | ||
| 49 | if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase)) | ||
| 50 | { | ||
| 51 | return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine; | ||
| 52 | } | ||
| 53 | |||
| 54 | return null; | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
diff --git a/src/wix/test/Example.Extension/ExampleRow.cs b/src/wix/test/Example.Extension/ExampleRow.cs new file mode 100644 index 00000000..fc20c6c9 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleRow.cs | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | using WixToolset.Data.WindowsInstaller; | ||
| 7 | |||
| 8 | public class ExampleRow : Row | ||
| 9 | { | ||
| 10 | public ExampleRow(SourceLineNumber sourceLineNumbers, Table table) | ||
| 11 | : base(sourceLineNumbers, table) | ||
| 12 | { | ||
| 13 | } | ||
| 14 | |||
| 15 | public ExampleRow(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) | ||
| 16 | : base(sourceLineNumbers, tableDefinition) | ||
| 17 | { | ||
| 18 | } | ||
| 19 | |||
| 20 | public string Example | ||
| 21 | { | ||
| 22 | get { return (string)this.Fields[0].Data; } | ||
| 23 | set { this.Fields[0].Data = value; } | ||
| 24 | } | ||
| 25 | |||
| 26 | public string Value | ||
| 27 | { | ||
| 28 | get { return (string)this.Fields[1].Data; } | ||
| 29 | set { this.Fields[1].Data = value; } | ||
| 30 | } | ||
| 31 | } | ||
| 32 | } | ||
diff --git a/src/wix/test/Example.Extension/ExampleSearchSymbol.cs b/src/wix/test/Example.Extension/ExampleSearchSymbol.cs new file mode 100644 index 00000000..40a39292 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleSearchSymbol.cs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | public enum ExampleSearchSymbolFields | ||
| 8 | { | ||
| 9 | SearchFor, | ||
| 10 | } | ||
| 11 | |||
| 12 | public class ExampleSearchSymbol : IntermediateSymbol | ||
| 13 | { | ||
| 14 | public ExampleSearchSymbol() : base(ExampleSymbolDefinitions.ExampleSearch, null, null) | ||
| 15 | { | ||
| 16 | } | ||
| 17 | |||
| 18 | public ExampleSearchSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.ExampleSearch, sourceLineNumber, id) | ||
| 19 | { | ||
| 20 | } | ||
| 21 | |||
| 22 | public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; | ||
| 23 | |||
| 24 | public string SearchFor | ||
| 25 | { | ||
| 26 | get => this.Fields[(int)ExampleSearchSymbolFields.SearchFor]?.AsString(); | ||
| 27 | set => this.Set((int)ExampleSearchSymbolFields.SearchFor, value); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/wix/test/Example.Extension/ExampleSymbol.cs b/src/wix/test/Example.Extension/ExampleSymbol.cs new file mode 100644 index 00000000..314087e9 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleSymbol.cs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | public enum ExampleSymbolFields | ||
| 8 | { | ||
| 9 | Value, | ||
| 10 | } | ||
| 11 | |||
| 12 | public class ExampleSymbol : IntermediateSymbol | ||
| 13 | { | ||
| 14 | public ExampleSymbol() : base(ExampleSymbolDefinitions.Example, null, null) | ||
| 15 | { | ||
| 16 | } | ||
| 17 | |||
| 18 | public ExampleSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.Example, sourceLineNumber, id) | ||
| 19 | { | ||
| 20 | } | ||
| 21 | |||
| 22 | public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; | ||
| 23 | |||
| 24 | public string Value | ||
| 25 | { | ||
| 26 | get => this.Fields[(int)ExampleSymbolFields.Value]?.AsString(); | ||
| 27 | set => this.Set((int)ExampleSymbolFields.Value, value); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs b/src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs new file mode 100644 index 00000000..f13d716d --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs | |||
| @@ -0,0 +1,67 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.Burn; | ||
| 8 | |||
| 9 | public enum ExampleSymbolDefinitionType | ||
| 10 | { | ||
| 11 | Example, | ||
| 12 | ExampleSearch, | ||
| 13 | } | ||
| 14 | |||
| 15 | public static class ExampleSymbolDefinitions | ||
| 16 | { | ||
| 17 | public static readonly IntermediateSymbolDefinition Example = new IntermediateSymbolDefinition( | ||
| 18 | ExampleSymbolDefinitionType.Example.ToString(), | ||
| 19 | new[] | ||
| 20 | { | ||
| 21 | new IntermediateFieldDefinition(nameof(ExampleSymbolFields.Value), IntermediateFieldType.String), | ||
| 22 | }, | ||
| 23 | typeof(ExampleSymbol)); | ||
| 24 | |||
| 25 | public static readonly IntermediateSymbolDefinition ExampleSearch = new IntermediateSymbolDefinition( | ||
| 26 | ExampleSymbolDefinitionType.ExampleSearch.ToString(), | ||
| 27 | new[] | ||
| 28 | { | ||
| 29 | new IntermediateFieldDefinition(nameof(ExampleSearchSymbolFields.SearchFor), IntermediateFieldType.String), | ||
| 30 | }, | ||
| 31 | typeof(ExampleSearchSymbol)); | ||
| 32 | |||
| 33 | static ExampleSymbolDefinitions() | ||
| 34 | { | ||
| 35 | ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag); | ||
| 36 | } | ||
| 37 | |||
| 38 | public static bool TryGetSymbolType(string name, out ExampleSymbolDefinitionType type) | ||
| 39 | { | ||
| 40 | return Enum.TryParse(name, out type); | ||
| 41 | } | ||
| 42 | |||
| 43 | public static IntermediateSymbolDefinition ByName(string name) | ||
| 44 | { | ||
| 45 | if (!TryGetSymbolType(name, out var type)) | ||
| 46 | { | ||
| 47 | return null; | ||
| 48 | } | ||
| 49 | return ByType(type); | ||
| 50 | } | ||
| 51 | |||
| 52 | public static IntermediateSymbolDefinition ByType(ExampleSymbolDefinitionType type) | ||
| 53 | { | ||
| 54 | switch (type) | ||
| 55 | { | ||
| 56 | case ExampleSymbolDefinitionType.Example: | ||
| 57 | return ExampleSymbolDefinitions.Example; | ||
| 58 | |||
| 59 | case ExampleSymbolDefinitionType.ExampleSearch: | ||
| 60 | return ExampleSymbolDefinitions.ExampleSearch; | ||
| 61 | |||
| 62 | default: | ||
| 63 | throw new ArgumentOutOfRangeException(nameof(type)); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
diff --git a/src/wix/test/Example.Extension/ExampleTableDefinitions.cs b/src/wix/test/Example.Extension/ExampleTableDefinitions.cs new file mode 100644 index 00000000..a2b81698 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleTableDefinitions.cs | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using WixToolset.Data.WindowsInstaller; | ||
| 6 | |||
| 7 | public static class ExampleTableDefinitions | ||
| 8 | { | ||
| 9 | public static readonly TableDefinition ExampleTable = new TableDefinition( | ||
| 10 | "Wix4Example", | ||
| 11 | ExampleSymbolDefinitions.Example, | ||
| 12 | new[] | ||
| 13 | { | ||
| 14 | new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), | ||
| 15 | new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), | ||
| 16 | }, | ||
| 17 | strongRowType: typeof(ExampleRow), | ||
| 18 | symbolIdIsPrimaryKey: true | ||
| 19 | ); | ||
| 20 | |||
| 21 | public static readonly TableDefinition NotInAll = new TableDefinition( | ||
| 22 | "TableDefinitionNotExposedByExtension", | ||
| 23 | null, | ||
| 24 | new[] | ||
| 25 | { | ||
| 26 | new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), | ||
| 27 | new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), | ||
| 28 | }, | ||
| 29 | symbolIdIsPrimaryKey: true | ||
| 30 | ); | ||
| 31 | |||
| 32 | public static readonly TableDefinition[] All = new[] { ExampleTable }; | ||
| 33 | } | ||
| 34 | } | ||
diff --git a/src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs b/src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs new file mode 100644 index 00000000..afccc56f --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.WindowsInstaller; | ||
| 8 | using WixToolset.Extensibility; | ||
| 9 | |||
| 10 | internal class ExampleWindowsInstallerBackendExtension : BaseWindowsInstallerBackendBinderExtension | ||
| 11 | { | ||
| 12 | public override IReadOnlyCollection<TableDefinition> TableDefinitions => ExampleTableDefinitions.All; | ||
| 13 | |||
| 14 | public override bool TryProcessSymbol(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData output, TableDefinitionCollection tableDefinitions) | ||
| 15 | { | ||
| 16 | if (ExampleSymbolDefinitions.TryGetSymbolType(symbol.Definition.Name, out var symbolType)) | ||
| 17 | { | ||
| 18 | switch (symbolType) | ||
| 19 | { | ||
| 20 | case ExampleSymbolDefinitionType.Example: | ||
| 21 | { | ||
| 22 | var row = (ExampleRow)this.BackendHelper.CreateRow(section, symbol, output, ExampleTableDefinitions.ExampleTable); | ||
| 23 | row.Example = symbol.Id.Id; | ||
| 24 | row.Value = symbol[0].AsString(); | ||
| 25 | } | ||
| 26 | return true; | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | return base.TryProcessSymbol(section, symbol, output, tableDefinitions); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs b/src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs new file mode 100644 index 00000000..a83da7f6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Core.Burn.Bundles; | ||
| 7 | using Xunit; | ||
| 8 | |||
| 9 | public class BurnReaderFixture | ||
| 10 | { | ||
| 11 | [Fact] | ||
| 12 | public void CanReadUInt16Max() | ||
| 13 | { | ||
| 14 | var bytes = new byte[] { 0xFF, 0xFF }; | ||
| 15 | var offset = 0u; | ||
| 16 | |||
| 17 | var result = BurnCommon.ReadUInt16(bytes, offset); | ||
| 18 | |||
| 19 | Assert.Equal(UInt16.MaxValue, result); | ||
| 20 | } | ||
| 21 | |||
| 22 | [Fact] | ||
| 23 | public void CanReadUInt32Max() | ||
| 24 | { | ||
| 25 | var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; | ||
| 26 | var offset = 0u; | ||
| 27 | |||
| 28 | var result = BurnCommon.ReadUInt32(bytes, offset); | ||
| 29 | |||
| 30 | Assert.Equal(UInt32.MaxValue, result); | ||
| 31 | } | ||
| 32 | |||
| 33 | [Fact] | ||
| 34 | public void CanReadUInt64Max() | ||
| 35 | { | ||
| 36 | var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; | ||
| 37 | var offset = 0u; | ||
| 38 | |||
| 39 | var result = BurnCommon.ReadUInt64(bytes, offset); | ||
| 40 | |||
| 41 | Assert.Equal(UInt64.MaxValue, result); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj b/src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj new file mode 100644 index 00000000..175ee1a9 --- /dev/null +++ b/src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFramework>netcoreapp3.1</TargetFramework> | ||
| 7 | <IsPackable>false</IsPackable> | ||
| 8 | <DebugType>embedded</DebugType> | ||
| 9 | </PropertyGroup> | ||
| 10 | |||
| 11 | <PropertyGroup> | ||
| 12 | <NoWarn>NU1701</NoWarn> | ||
| 13 | </PropertyGroup> | ||
| 14 | |||
| 15 | <ItemGroup> | ||
| 16 | <ProjectReference Include="..\..\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj" /> | ||
| 17 | </ItemGroup> | ||
| 18 | |||
| 19 | <ItemGroup> | ||
| 20 | <PackageReference Include="WixBuildTools.TestSupport" Version="4.0.*" /> | ||
| 21 | </ItemGroup> | ||
| 22 | |||
| 23 | <ItemGroup> | ||
| 24 | <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" /> | ||
| 25 | <PackageReference Include="xunit" Version="2.4.1" /> | ||
| 26 | <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" PrivateAssets="All" /> | ||
| 27 | </ItemGroup> | ||
| 28 | </Project> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs new file mode 100644 index 00000000..47b47ef5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixBuildTools.TestSupport; | ||
| 7 | using WixToolset.Core.TestPackage; | ||
| 8 | using Xunit; | ||
| 9 | |||
| 10 | public class ApprovedExeFixture | ||
| 11 | { | ||
| 12 | [Fact] | ||
| 13 | public void CanBuildWithApprovedExe() | ||
| 14 | { | ||
| 15 | var folder = TestData.Get(@"TestData"); | ||
| 16 | |||
| 17 | using (var fs = new DisposableFileSystem()) | ||
| 18 | { | ||
| 19 | var baseFolder = fs.GetFolder(); | ||
| 20 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 21 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 22 | |||
| 23 | var result = WixRunner.Execute(new[] | ||
| 24 | { | ||
| 25 | "build", | ||
| 26 | Path.Combine(folder, "BundleWithApprovedExe", "Bundle.wxs"), | ||
| 27 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 28 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 29 | "-intermediateFolder", intermediateFolder, | ||
| 30 | "-o", exePath, | ||
| 31 | }); | ||
| 32 | |||
| 33 | Assert.NotEqual(0, result.ExitCode); | ||
| 34 | Assert.False(File.Exists(exePath)); | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | [Fact] | ||
| 39 | public void CanBuildWithApprovedExe64() | ||
| 40 | { | ||
| 41 | var folder = TestData.Get(@"TestData"); | ||
| 42 | |||
| 43 | using (var fs = new DisposableFileSystem()) | ||
| 44 | { | ||
| 45 | var baseFolder = fs.GetFolder(); | ||
| 46 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 47 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 48 | |||
| 49 | var result = WixRunner.Execute(new[] | ||
| 50 | { | ||
| 51 | "build", | ||
| 52 | Path.Combine(folder, "BundleWithApprovedExe", "Bundle64.wxs"), | ||
| 53 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 54 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 55 | "-intermediateFolder", intermediateFolder, | ||
| 56 | "-o", exePath, | ||
| 57 | }); | ||
| 58 | |||
| 59 | Assert.NotEqual(0, result.ExitCode); | ||
| 60 | Assert.False(File.Exists(exePath)); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs new file mode 100644 index 00000000..62ffe1eb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs | |||
| @@ -0,0 +1,148 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core.TestPackage; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using Xunit; | ||
| 11 | |||
| 12 | public class BadInputFixture | ||
| 13 | { | ||
| 14 | [Fact] | ||
| 15 | public void SwitchIsNotConsideredAnArgument() | ||
| 16 | { | ||
| 17 | var result = WixRunner.Execute(new[] | ||
| 18 | { | ||
| 19 | "build", | ||
| 20 | "-bindpath", "-thisisaswitchnotanarg", | ||
| 21 | }); | ||
| 22 | |||
| 23 | Assert.Single(result.Messages, m => m.Id == (int)ErrorMessages.Ids.ExpectedArgument); | ||
| 24 | // TODO: when CantBuildSingleExeBundleWithInvalidArgument is fixed, uncomment: | ||
| 25 | //Assert.Equal((int)ErrorMessages.Ids.ExpectedArgument, result.ExitCode); | ||
| 26 | } | ||
| 27 | |||
| 28 | [Fact] | ||
| 29 | public void HandleInvalidIds() | ||
| 30 | { | ||
| 31 | var folder = TestData.Get(@"TestData\BadInput"); | ||
| 32 | |||
| 33 | using (var fs = new DisposableFileSystem()) | ||
| 34 | { | ||
| 35 | var baseFolder = fs.GetFolder(); | ||
| 36 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 37 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 38 | |||
| 39 | var result = WixRunner.Execute(new[] | ||
| 40 | { | ||
| 41 | "build", | ||
| 42 | Path.Combine(folder, "InvalidIds.wxs"), | ||
| 43 | "-intermediateFolder", intermediateFolder, | ||
| 44 | "-o", wixlibPath, | ||
| 45 | }); | ||
| 46 | |||
| 47 | Assert.Equal(330, result.ExitCode); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | [Fact] | ||
| 52 | public void CantBuildSingleExeBundleWithInvalidArgument() | ||
| 53 | { | ||
| 54 | var folder = TestData.Get(@"TestData"); | ||
| 55 | |||
| 56 | using (var fs = new DisposableFileSystem()) | ||
| 57 | { | ||
| 58 | var baseFolder = fs.GetFolder(); | ||
| 59 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 60 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 61 | |||
| 62 | var result = WixRunner.Execute(new[] | ||
| 63 | { | ||
| 64 | "build", | ||
| 65 | Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), | ||
| 66 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 67 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 68 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 69 | "-intermediateFolder", intermediateFolder, | ||
| 70 | "-o", exePath, | ||
| 71 | "-nonexistentswitch", "param", | ||
| 72 | }); | ||
| 73 | |||
| 74 | Assert.NotEqual(0, result.ExitCode); | ||
| 75 | Assert.False(File.Exists(exePath)); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | [Fact] | ||
| 80 | public void RegistryKeyWithoutAttributesDoesntCrash() | ||
| 81 | { | ||
| 82 | var folder = TestData.Get(@"TestData\BadInput"); | ||
| 83 | |||
| 84 | using (var fs = new DisposableFileSystem()) | ||
| 85 | { | ||
| 86 | var baseFolder = fs.GetFolder(); | ||
| 87 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 88 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 89 | |||
| 90 | var result = WixRunner.Execute(new[] | ||
| 91 | { | ||
| 92 | "build", | ||
| 93 | Path.Combine(folder, "RegistryKey.wxs"), | ||
| 94 | "-intermediateFolder", intermediateFolder, | ||
| 95 | "-o", wixlibPath, | ||
| 96 | }); | ||
| 97 | |||
| 98 | Assert.InRange(result.ExitCode, 2, Int32.MaxValue); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | [Fact] | ||
| 103 | public void BundleVariableWithBadTypeIsRejected() | ||
| 104 | { | ||
| 105 | var folder = TestData.Get(@"TestData\BadInput"); | ||
| 106 | |||
| 107 | using (var fs = new DisposableFileSystem()) | ||
| 108 | { | ||
| 109 | var baseFolder = fs.GetFolder(); | ||
| 110 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 111 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 112 | |||
| 113 | var result = WixRunner.Execute(new[] | ||
| 114 | { | ||
| 115 | "build", | ||
| 116 | Path.Combine(folder, "BundleVariable.wxs"), | ||
| 117 | "-intermediateFolder", intermediateFolder, | ||
| 118 | "-o", wixlibPath, | ||
| 119 | }); | ||
| 120 | |||
| 121 | Assert.Equal(21, result.ExitCode); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | [Fact] | ||
| 126 | public void BundleVariableWithHiddenPersistedIsRejected() | ||
| 127 | { | ||
| 128 | var folder = TestData.Get(@"TestData\BadInput"); | ||
| 129 | |||
| 130 | using (var fs = new DisposableFileSystem()) | ||
| 131 | { | ||
| 132 | var baseFolder = fs.GetFolder(); | ||
| 133 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 134 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 135 | |||
| 136 | var result = WixRunner.Execute(new[] | ||
| 137 | { | ||
| 138 | "build", | ||
| 139 | Path.Combine(folder, "HiddenPersistedBundleVariable.wxs"), | ||
| 140 | "-intermediateFolder", intermediateFolder, | ||
| 141 | "-o", wixlibPath, | ||
| 142 | }); | ||
| 143 | |||
| 144 | Assert.Equal(193, result.ExitCode); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs new file mode 100644 index 00000000..39e6b4aa --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core.TestPackage; | ||
| 9 | using Xunit; | ||
| 10 | |||
| 11 | public class BindVariablesFixture | ||
| 12 | { | ||
| 13 | [Fact] | ||
| 14 | public void CanBuildBundleWithPackageBindVariables() | ||
| 15 | { | ||
| 16 | var folder = TestData.Get(@"TestData"); | ||
| 17 | |||
| 18 | using (var fs = new DisposableFileSystem()) | ||
| 19 | { | ||
| 20 | var baseFolder = fs.GetFolder(); | ||
| 21 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 22 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 23 | |||
| 24 | var result = WixRunner.Execute(new[] | ||
| 25 | { | ||
| 26 | "build", | ||
| 27 | Path.Combine(folder, "BundleBindVariables", "CacheIdFromPackageDescription.wxs"), | ||
| 28 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 29 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 30 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 31 | "-intermediateFolder", intermediateFolder, | ||
| 32 | "-o", exePath, | ||
| 33 | }); | ||
| 34 | |||
| 35 | result.AssertSuccess(); | ||
| 36 | |||
| 37 | Assert.True(File.Exists(exePath)); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | [Fact] | ||
| 42 | public void CanBuildWithDefaultValue() | ||
| 43 | { | ||
| 44 | var folder = TestData.Get(@"TestData", "BindVariables"); | ||
| 45 | |||
| 46 | using (var fs = new DisposableFileSystem()) | ||
| 47 | { | ||
| 48 | var baseFolder = fs.GetFolder(); | ||
| 49 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 50 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 51 | |||
| 52 | var result = WixRunner.Execute(new[] | ||
| 53 | { | ||
| 54 | "build", | ||
| 55 | Path.Combine(folder, "DefaultedVariable.wxs"), | ||
| 56 | "-bf", | ||
| 57 | "-intermediateFolder", intermediateFolder, | ||
| 58 | "-bindpath", folder, | ||
| 59 | "-o", wixlibPath, | ||
| 60 | }); | ||
| 61 | |||
| 62 | result.AssertSuccess(); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | [Fact] | ||
| 67 | public void CannotBuildWixlibWithBinariesFromMissingNamedBindPaths() | ||
| 68 | { | ||
| 69 | var folder = TestData.Get(@"TestData", "WixlibWithBinaries"); | ||
| 70 | |||
| 71 | using (var fs = new DisposableFileSystem()) | ||
| 72 | { | ||
| 73 | var baseFolder = fs.GetFolder(); | ||
| 74 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 75 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 76 | |||
| 77 | var result = WixRunner.Execute(new[] | ||
| 78 | { | ||
| 79 | "build", | ||
| 80 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 81 | "-bf", | ||
| 82 | "-bindpath", Path.Combine(folder, "data"), | ||
| 83 | // Use names that aren't excluded in default .gitignores. | ||
| 84 | "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", | ||
| 85 | "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", | ||
| 86 | "-bindpath", $"{Path.Combine(folder, "data", "alpha")}", | ||
| 87 | "-bindpath", $"{Path.Combine(folder, "data", "powerpc")}", | ||
| 88 | "-intermediateFolder", intermediateFolder, | ||
| 89 | "-o", wixlibPath, | ||
| 90 | }); | ||
| 91 | |||
| 92 | Assert.Equal(103, result.ExitCode); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs new file mode 100644 index 00000000..9bdc9496 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Linq; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core.TestPackage; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using Xunit; | ||
| 12 | |||
| 13 | public class BootstrapperApplicationFixture | ||
| 14 | { | ||
| 15 | [Fact] | ||
| 16 | public void CanSetBootstrapperApplicationDllDpiAwareness() | ||
| 17 | { | ||
| 18 | var folder = TestData.Get(@"TestData\BootstrapperApplication"); | ||
| 19 | |||
| 20 | using (var fs = new DisposableFileSystem()) | ||
| 21 | { | ||
| 22 | var baseFolder = fs.GetFolder(); | ||
| 23 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 24 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 25 | |||
| 26 | var result = WixRunner.Execute(new[] | ||
| 27 | { | ||
| 28 | "build", | ||
| 29 | Path.Combine(folder, "DpiAwareness.wxs"), | ||
| 30 | "-intermediateFolder", intermediateFolder, | ||
| 31 | "-o", wixlibPath, | ||
| 32 | }); | ||
| 33 | |||
| 34 | result.AssertSuccess(); | ||
| 35 | |||
| 36 | var intermediate = Intermediate.Load(wixlibPath); | ||
| 37 | var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); | ||
| 38 | var baDllSymbol = allSymbols.OfType<WixBootstrapperApplicationDllSymbol>() | ||
| 39 | .SingleOrDefault(); | ||
| 40 | Assert.NotNull(baDllSymbol); | ||
| 41 | |||
| 42 | Assert.Equal(WixBootstrapperApplicationDpiAwarenessType.GdiScaled, baDllSymbol.DpiAwareness); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 46 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs new file mode 100644 index 00000000..b33b8891 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Linq; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core; | ||
| 9 | using WixToolset.Core.TestPackage; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Extensibility.Services; | ||
| 12 | using Xunit; | ||
| 13 | |||
| 14 | public class BundleExtractionFixture | ||
| 15 | { | ||
| 16 | [Fact] | ||
| 17 | public void CanExtractBundleWithDetachedContainer() | ||
| 18 | { | ||
| 19 | var folder = TestData.Get(@"TestData"); | ||
| 20 | |||
| 21 | using (var fs = new DisposableFileSystem()) | ||
| 22 | { | ||
| 23 | var baseFolder = fs.GetFolder(); | ||
| 24 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 25 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 26 | var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); | ||
| 27 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 28 | var baFolderPath = Path.Combine(extractFolderPath, "UX"); | ||
| 29 | var attachedContainerFolderPath = Path.Combine(extractFolderPath, "AttachedContainer"); | ||
| 30 | |||
| 31 | // TODO: use WixRunner.Execute(string[]) to always go through the command line. | ||
| 32 | var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); | ||
| 33 | var result = WixRunner.Execute(new[] | ||
| 34 | { | ||
| 35 | "build", | ||
| 36 | Path.Combine(folder, "BundleWithDetachedContainer", "Bundle.wxs"), | ||
| 37 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 38 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 39 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 40 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 41 | "-intermediateFolder", intermediateFolder, | ||
| 42 | "-o", exePath, | ||
| 43 | }, serviceProvider, out var messages).Result; | ||
| 44 | |||
| 45 | WixRunnerResult.AssertSuccess(result, messages); | ||
| 46 | Assert.Empty(messages.Where(m => m.Level == MessageLevel.Warning)); | ||
| 47 | |||
| 48 | Assert.True(File.Exists(exePath)); | ||
| 49 | |||
| 50 | var unbinder = serviceProvider.GetService<IUnbinder>(); | ||
| 51 | unbinder.Unbind(exePath, OutputType.Bundle, extractFolderPath); | ||
| 52 | |||
| 53 | Assert.True(File.Exists(Path.Combine(baFolderPath, "manifest.xml"))); | ||
| 54 | Assert.False(Directory.Exists(attachedContainerFolderPath)); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs new file mode 100644 index 00000000..ab644080 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs | |||
| @@ -0,0 +1,478 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Text; | ||
| 10 | using System.Xml; | ||
| 11 | using Example.Extension; | ||
| 12 | using WixBuildTools.TestSupport; | ||
| 13 | using WixToolset.Core; | ||
| 14 | using WixToolset.Core.Burn; | ||
| 15 | using WixToolset.Core.TestPackage; | ||
| 16 | using WixToolset.Data; | ||
| 17 | using WixToolset.Data.Burn; | ||
| 18 | using WixToolset.Data.Symbols; | ||
| 19 | using WixToolset.Dtf.Resources; | ||
| 20 | using Xunit; | ||
| 21 | |||
| 22 | public class BundleFixture | ||
| 23 | { | ||
| 24 | [Fact] | ||
| 25 | public void CanBuildMultiFileBundle() | ||
| 26 | { | ||
| 27 | var folder = TestData.Get(@"TestData\SimpleBundle"); | ||
| 28 | |||
| 29 | using (var fs = new DisposableFileSystem()) | ||
| 30 | { | ||
| 31 | var baseFolder = fs.GetFolder(); | ||
| 32 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 33 | |||
| 34 | var result = WixRunner.Execute(new[] | ||
| 35 | { | ||
| 36 | "build", | ||
| 37 | Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), | ||
| 38 | Path.Combine(folder, "MultiFileBundle.wxs"), | ||
| 39 | "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), | ||
| 40 | "-bindpath", Path.Combine(folder, "data"), | ||
| 41 | "-intermediateFolder", intermediateFolder, | ||
| 42 | "-o", Path.Combine(baseFolder, @"bin\test.exe") | ||
| 43 | }); | ||
| 44 | |||
| 45 | result.AssertSuccess(); | ||
| 46 | |||
| 47 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); | ||
| 48 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | [Fact] | ||
| 53 | public void CanBuildSimpleBundle() | ||
| 54 | { | ||
| 55 | var folder = TestData.Get(@"TestData\SimpleBundle"); | ||
| 56 | |||
| 57 | using (var fs = new DisposableFileSystem()) | ||
| 58 | { | ||
| 59 | var baseFolder = fs.GetFolder(); | ||
| 60 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 61 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 62 | var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); | ||
| 63 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 64 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 65 | |||
| 66 | var result = WixRunner.Execute(new[] | ||
| 67 | { | ||
| 68 | "build", | ||
| 69 | Path.Combine(folder, "Bundle.wxs"), | ||
| 70 | "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), | ||
| 71 | "-bindpath", Path.Combine(folder, "data"), | ||
| 72 | "-intermediateFolder", intermediateFolder, | ||
| 73 | "-o", exePath, | ||
| 74 | }); | ||
| 75 | |||
| 76 | result.AssertSuccess(); | ||
| 77 | Assert.Empty(result.Messages.Where(m => m.Level == MessageLevel.Warning)); | ||
| 78 | |||
| 79 | Assert.True(File.Exists(exePath)); | ||
| 80 | Assert.True(File.Exists(pdbPath)); | ||
| 81 | |||
| 82 | using (var wixOutput = WixOutput.Read(pdbPath)) | ||
| 83 | { | ||
| 84 | |||
| 85 | var intermediate = Intermediate.Load(wixOutput); | ||
| 86 | var section = intermediate.Sections.Single(); | ||
| 87 | |||
| 88 | var bundleSymbol = section.Symbols.OfType<WixBundleSymbol>().Single(); | ||
| 89 | Assert.Equal("1.0.0.0", bundleSymbol.Version); | ||
| 90 | |||
| 91 | var previousVersion = bundleSymbol.Fields[(int)WixBundleSymbolFields.Version].PreviousValue; | ||
| 92 | Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); | ||
| 93 | |||
| 94 | var msiSymbol = section.Symbols.OfType<WixBundlePackageSymbol>().Single(); | ||
| 95 | Assert.Equal("test.msi", msiSymbol.Id.Id); | ||
| 96 | |||
| 97 | var extractResult = BundleExtractor.ExtractBAContainer(null, exePath, baFolderPath, extractFolderPath); | ||
| 98 | extractResult.AssertSuccess(); | ||
| 99 | |||
| 100 | var burnManifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); | ||
| 101 | var extractedBurnManifestData = File.ReadAllText(Path.Combine(baFolderPath, "manifest.xml"), Encoding.UTF8); | ||
| 102 | Assert.Equal(extractedBurnManifestData, burnManifestData); | ||
| 103 | |||
| 104 | var baManifestData = wixOutput.GetData(BurnConstants.BootstrapperApplicationDataWixOutputStreamName); | ||
| 105 | var extractedBaManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BootstrapperApplicationData.xml"), Encoding.UTF8); | ||
| 106 | Assert.Equal(extractedBaManifestData, baManifestData); | ||
| 107 | |||
| 108 | var bextManifestData = wixOutput.GetData(BurnConstants.BundleExtensionDataWixOutputStreamName); | ||
| 109 | var extractedBextManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BundleExtensionData.xml"), Encoding.UTF8); | ||
| 110 | Assert.Equal(extractedBextManifestData, bextManifestData); | ||
| 111 | |||
| 112 | var logElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Log"); | ||
| 113 | var logElement = (XmlNode)Assert.Single(logElements); | ||
| 114 | Assert.Equal("<Log PathVariable='WixBundleLog' Prefix='~TestBundle' Extension='log' />", logElement.GetTestXml()); | ||
| 115 | |||
| 116 | var registrationElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration"); | ||
| 117 | var registrationElement = (XmlNode)Assert.Single(registrationElements); | ||
| 118 | Assert.Equal($"<Registration Id='{bundleSymbol.BundleId}' ExecutableName='test.exe' PerMachine='yes' Tag='' Version='1.0.0.0' ProviderKey='{bundleSymbol.BundleId}'>" + | ||
| 119 | "<Arp Register='yes' DisplayName='~TestBundle' DisplayVersion='1.0.0.0' Publisher='Example Corporation' />" + | ||
| 120 | "</Registration>", registrationElement.GetTestXml()); | ||
| 121 | |||
| 122 | var msiPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='test.msi']"); | ||
| 123 | var msiPayload = (XmlNode)Assert.Single(msiPayloads); | ||
| 124 | Assert.Equal("<Payload Id='test.msi' FilePath='test.msi' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a0' Container='WixAttachedContainer' />", | ||
| 125 | msiPayload.GetTestXml(new Dictionary<string, List<string>>() { { "Payload", new List<string> { "FileSize", "Hash" } } })); | ||
| 126 | } | ||
| 127 | |||
| 128 | var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); | ||
| 129 | manifestResource.Load(exePath); | ||
| 130 | var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); | ||
| 131 | Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + | ||
| 132 | "<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">" + | ||
| 133 | "<assemblyIdentity name=\"test.exe\" version=\"1.0.0.0\" processorArchitecture=\"x86\" type=\"win32\" />" + | ||
| 134 | "<description>~TestBundle</description>" + | ||
| 135 | "<dependency><dependentAssembly><assemblyIdentity name=\"Microsoft.Windows.Common-Controls\" version=\"6.0.0.0\" processorArchitecture=\"x86\" publicKeyToken=\"6595b64144ccf1df\" language=\"*\" type=\"win32\" /></dependentAssembly></dependency>" + | ||
| 136 | "<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\"><application><supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\" /><supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" /><supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" /><supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" /><supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" /></application></compatibility>" + | ||
| 137 | "<trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v3\"><security><requestedPrivileges><requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" /></requestedPrivileges></security></trustInfo>" + | ||
| 138 | "<application xmlns=\"urn:schemas-microsoft-com:asm.v3\"><windowsSettings><dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true/pm</dpiAware><dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitorV2, PerMonitor</dpiAwareness></windowsSettings></application>" + | ||
| 139 | "</assembly>", actualManifestData); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | [Fact] | ||
| 144 | public void CanBuildX64Bundle() | ||
| 145 | { | ||
| 146 | var folder = TestData.Get(@"TestData\SimpleBundle"); | ||
| 147 | |||
| 148 | using (var fs = new DisposableFileSystem()) | ||
| 149 | { | ||
| 150 | var baseFolder = fs.GetFolder(); | ||
| 151 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 152 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 153 | var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); | ||
| 154 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 155 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 156 | |||
| 157 | var result = WixRunner.Execute(false, new[] // TODO: go back to elevating warnings as errors. | ||
| 158 | { | ||
| 159 | "build", | ||
| 160 | "-arch", "x64", | ||
| 161 | Path.Combine(folder, "Bundle.wxs"), | ||
| 162 | "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), | ||
| 163 | "-bindpath", Path.Combine(folder, "data"), | ||
| 164 | "-intermediateFolder", intermediateFolder, | ||
| 165 | "-o", exePath, | ||
| 166 | }); | ||
| 167 | |||
| 168 | result.AssertSuccess(); | ||
| 169 | var warning = Assert.Single(result.Messages.Where(m => m.Level == MessageLevel.Warning)); | ||
| 170 | Assert.Equal((int)WarningMessages.Ids.ExperimentalBundlePlatform, warning.Id); | ||
| 171 | |||
| 172 | Assert.True(File.Exists(exePath)); | ||
| 173 | Assert.True(File.Exists(pdbPath)); | ||
| 174 | |||
| 175 | var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); | ||
| 176 | manifestResource.Load(exePath); | ||
| 177 | var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); | ||
| 178 | Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + | ||
| 179 | "<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">" + | ||
| 180 | "<assemblyIdentity name=\"test.exe\" version=\"1.0.0.0\" processorArchitecture=\"amd64\" type=\"win32\" />" + | ||
| 181 | "<description>~TestBundle</description>" + | ||
| 182 | "<dependency><dependentAssembly><assemblyIdentity name=\"Microsoft.Windows.Common-Controls\" version=\"6.0.0.0\" processorArchitecture=\"amd64\" publicKeyToken=\"6595b64144ccf1df\" language=\"*\" type=\"win32\" /></dependentAssembly></dependency>" + | ||
| 183 | "<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\"><application><supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\" /><supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" /><supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" /><supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" /><supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" /></application></compatibility>" + | ||
| 184 | "<trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v3\"><security><requestedPrivileges><requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" /></requestedPrivileges></security></trustInfo>" + | ||
| 185 | "<application xmlns=\"urn:schemas-microsoft-com:asm.v3\"><windowsSettings><dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true/pm</dpiAware><dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitorV2, PerMonitor</dpiAwareness></windowsSettings></application>" + | ||
| 186 | "</assembly>", actualManifestData); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | [Fact] | ||
| 191 | public void CanBuildSimpleBundleUsingExtensionBA() | ||
| 192 | { | ||
| 193 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 194 | var folder = TestData.Get(@"TestData\SimpleBundle"); | ||
| 195 | |||
| 196 | using (var fs = new DisposableFileSystem()) | ||
| 197 | { | ||
| 198 | var baseFolder = fs.GetFolder(); | ||
| 199 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 200 | |||
| 201 | var result = WixRunner.Execute(new[] | ||
| 202 | { | ||
| 203 | "build", | ||
| 204 | Path.Combine(folder, "MultiFileBundle.wxs"), | ||
| 205 | "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), | ||
| 206 | "-ext", extensionPath, | ||
| 207 | "-bindpath", Path.Combine(folder, "data"), | ||
| 208 | "-intermediateFolder", intermediateFolder, | ||
| 209 | "-o", Path.Combine(baseFolder, @"bin\test.exe") | ||
| 210 | }); | ||
| 211 | |||
| 212 | result.AssertSuccess(); | ||
| 213 | |||
| 214 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); | ||
| 215 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | [Fact] | ||
| 220 | public void CanBuildSingleExeBundle() | ||
| 221 | { | ||
| 222 | var folder = TestData.Get(@"TestData"); | ||
| 223 | |||
| 224 | using (var fs = new DisposableFileSystem()) | ||
| 225 | { | ||
| 226 | var baseFolder = fs.GetFolder(); | ||
| 227 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 228 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 229 | |||
| 230 | var result = WixRunner.Execute(new[] | ||
| 231 | { | ||
| 232 | "build", | ||
| 233 | Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), | ||
| 234 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 235 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 236 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 237 | "-intermediateFolder", intermediateFolder, | ||
| 238 | "-o", exePath, | ||
| 239 | }); | ||
| 240 | |||
| 241 | result.AssertSuccess(); | ||
| 242 | |||
| 243 | Assert.True(File.Exists(exePath)); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | [Fact] | ||
| 248 | public void CanBuildSingleExeRemotePayloadBundle() | ||
| 249 | { | ||
| 250 | var folder = TestData.Get(@"TestData"); | ||
| 251 | |||
| 252 | using (var fs = new DisposableFileSystem()) | ||
| 253 | { | ||
| 254 | var baseFolder = fs.GetFolder(); | ||
| 255 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 256 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 257 | var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); | ||
| 258 | |||
| 259 | var result = WixRunner.Execute(new[] | ||
| 260 | { | ||
| 261 | "build", | ||
| 262 | Path.Combine(folder, "SingleExeBundle", "SingleExeRemotePayload.wxs"), | ||
| 263 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 264 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 265 | "-intermediateFolder", intermediateFolder, | ||
| 266 | "-o", exePath, | ||
| 267 | }); | ||
| 268 | |||
| 269 | result.AssertSuccess(); | ||
| 270 | |||
| 271 | Assert.True(File.Exists(exePath)); | ||
| 272 | Assert.True(File.Exists(pdbPath)); | ||
| 273 | |||
| 274 | using (var wixOutput = WixOutput.Read(pdbPath)) | ||
| 275 | { | ||
| 276 | var intermediate = Intermediate.Load(wixOutput); | ||
| 277 | var section = intermediate.Sections.Single(); | ||
| 278 | |||
| 279 | var payloadSymbol = section.Symbols.OfType<WixBundlePayloadSymbol>().Where(x => x.Id.Id == "NetFx462Web").Single(); | ||
| 280 | Assert.Equal(Int64.MaxValue, payloadSymbol.FileSize); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | [Fact] | ||
| 286 | public void CantBuildWithDuplicateCacheIds() | ||
| 287 | { | ||
| 288 | var folder = TestData.Get(@"TestData"); | ||
| 289 | |||
| 290 | using (var fs = new DisposableFileSystem()) | ||
| 291 | { | ||
| 292 | var baseFolder = fs.GetFolder(); | ||
| 293 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 294 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 295 | |||
| 296 | var result = WixRunner.Execute(new[] | ||
| 297 | { | ||
| 298 | "build", | ||
| 299 | Path.Combine(folder, "BadInput", "DuplicateCacheIds.wxs"), | ||
| 300 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 301 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 302 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 303 | "-intermediateFolder", intermediateFolder, | ||
| 304 | "-o", exePath, | ||
| 305 | }); | ||
| 306 | |||
| 307 | Assert.Equal(8001, result.ExitCode); | ||
| 308 | } | ||
| 309 | } | ||
| 310 | |||
| 311 | [Fact] | ||
| 312 | public void CantBuildWithDuplicatePayloadNames() | ||
| 313 | { | ||
| 314 | var folder = TestData.Get(@"TestData"); | ||
| 315 | |||
| 316 | using (var fs = new DisposableFileSystem()) | ||
| 317 | { | ||
| 318 | var baseFolder = fs.GetFolder(); | ||
| 319 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 320 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 321 | |||
| 322 | var result = WixRunner.Execute(new[] | ||
| 323 | { | ||
| 324 | "build", | ||
| 325 | Path.Combine(folder, "BadInput", "DuplicatePayloadNames.wxs"), | ||
| 326 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 327 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 328 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 329 | "-intermediateFolder", intermediateFolder, | ||
| 330 | "-o", exePath, | ||
| 331 | }); | ||
| 332 | |||
| 333 | var attachedContainerWarnings = result.Messages.Where(m => m.Id == (int)BurnBackendWarnings.Ids.AttachedContainerPayloadCollision) | ||
| 334 | .Select(m => m.ToString()) | ||
| 335 | .ToArray(); | ||
| 336 | WixAssert.CompareLineByLine(new string[] | ||
| 337 | { | ||
| 338 | "The Payload 'Auto2' has a duplicate Name 'burn.exe' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", | ||
| 339 | }, attachedContainerWarnings); | ||
| 340 | |||
| 341 | var baContainerErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.BAContainerPayloadCollision) | ||
| 342 | .Select(m => m.ToString()) | ||
| 343 | .ToArray(); | ||
| 344 | WixAssert.CompareLineByLine(new string[] | ||
| 345 | { | ||
| 346 | "The Payload 'DuplicatePayloadNames.wxs' has a duplicate Name 'fakeba.dll' in the BA container. When extracting the container at runtime, the file will get overwritten.", | ||
| 347 | "The Payload 'uxTxMXPVMXwQrPTMIGa5WGt93w0Ns' has a duplicate Name 'BootstrapperApplicationData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", | ||
| 348 | "The Payload 'uxYRbgitOs0K878jn5L_z7LdJ21KI' has a duplicate Name 'BundleExtensionData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", | ||
| 349 | }, baContainerErrors); | ||
| 350 | |||
| 351 | var externalErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.ExternalPayloadCollision) | ||
| 352 | .Select(m => m.ToString()) | ||
| 353 | .ToArray(); | ||
| 354 | WixAssert.CompareLineByLine(new string[] | ||
| 355 | { | ||
| 356 | "The external Payload 'HiddenPersistedBundleVariable.wxs' has a duplicate Name 'PayloadCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", | ||
| 357 | "The external Container 'MsiPackagesContainer' has a duplicate Name 'ContainerCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", | ||
| 358 | }, externalErrors); | ||
| 359 | |||
| 360 | var packageCacheErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.PackageCachePayloadCollision) | ||
| 361 | .Select(m => m.ToString()) | ||
| 362 | .ToArray(); | ||
| 363 | WixAssert.CompareLineByLine(new string[] | ||
| 364 | { | ||
| 365 | "The Payload 'test.msi' has a duplicate Name 'test.msi' in package 'test.msi'. When caching the package, the file will get overwritten.", | ||
| 366 | }, packageCacheErrors); | ||
| 367 | |||
| 368 | Assert.Equal(14, result.Messages.Length); | ||
| 369 | } | ||
| 370 | } | ||
| 371 | |||
| 372 | [Fact] | ||
| 373 | public void CantBuildWithOrphanPayload() | ||
| 374 | { | ||
| 375 | var folder = TestData.Get(@"TestData"); | ||
| 376 | |||
| 377 | using (var fs = new DisposableFileSystem()) | ||
| 378 | { | ||
| 379 | var baseFolder = fs.GetFolder(); | ||
| 380 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 381 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 382 | |||
| 383 | var result = WixRunner.Execute(new[] | ||
| 384 | { | ||
| 385 | "build", | ||
| 386 | Path.Combine(folder, "BadInput", "OrphanPayload.wxs"), | ||
| 387 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 388 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 389 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 390 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 391 | "-intermediateFolder", intermediateFolder, | ||
| 392 | "-o", exePath, | ||
| 393 | }); | ||
| 394 | |||
| 395 | Assert.Equal((int)LinkerErrors.Ids.OrphanedPayload, result.ExitCode); | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 399 | [Fact] | ||
| 400 | public void CantBuildWithPackageInMultipleContainers() | ||
| 401 | { | ||
| 402 | var folder = TestData.Get(@"TestData"); | ||
| 403 | |||
| 404 | using (var fs = new DisposableFileSystem()) | ||
| 405 | { | ||
| 406 | var baseFolder = fs.GetFolder(); | ||
| 407 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 408 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 409 | |||
| 410 | var result = WixRunner.Execute(new[] | ||
| 411 | { | ||
| 412 | "build", | ||
| 413 | Path.Combine(folder, "BadInput", "PackageInMultipleContainers.wxs"), | ||
| 414 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 415 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 416 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 417 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 418 | "-intermediateFolder", intermediateFolder, | ||
| 419 | "-o", exePath, | ||
| 420 | }); | ||
| 421 | |||
| 422 | Assert.Equal((int)LinkerErrors.Ids.PackageInMultipleContainers, result.ExitCode); | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | [Fact] | ||
| 427 | public void CantBuildWithUnscheduledPackage() | ||
| 428 | { | ||
| 429 | var folder = TestData.Get(@"TestData"); | ||
| 430 | |||
| 431 | using (var fs = new DisposableFileSystem()) | ||
| 432 | { | ||
| 433 | var baseFolder = fs.GetFolder(); | ||
| 434 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 435 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 436 | |||
| 437 | var result = WixRunner.Execute(new[] | ||
| 438 | { | ||
| 439 | "build", | ||
| 440 | Path.Combine(folder, "BadInput", "UnscheduledPackage.wxs"), | ||
| 441 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 442 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 443 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 444 | "-intermediateFolder", intermediateFolder, | ||
| 445 | "-o", exePath, | ||
| 446 | }); | ||
| 447 | |||
| 448 | Assert.Equal((int)LinkerErrors.Ids.UnscheduledChainPackage, result.ExitCode); | ||
| 449 | } | ||
| 450 | } | ||
| 451 | |||
| 452 | [Fact] | ||
| 453 | public void CantBuildWithUnscheduledRollbackBoundary() | ||
| 454 | { | ||
| 455 | var folder = TestData.Get(@"TestData"); | ||
| 456 | |||
| 457 | using (var fs = new DisposableFileSystem()) | ||
| 458 | { | ||
| 459 | var baseFolder = fs.GetFolder(); | ||
| 460 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 461 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 462 | |||
| 463 | var result = WixRunner.Execute(new[] | ||
| 464 | { | ||
| 465 | "build", | ||
| 466 | Path.Combine(folder, "BadInput", "UnscheduledRollbackBoundary.wxs"), | ||
| 467 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 468 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 469 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 470 | "-intermediateFolder", intermediateFolder, | ||
| 471 | "-o", exePath, | ||
| 472 | }); | ||
| 473 | |||
| 474 | Assert.Equal((int)LinkerErrors.Ids.UnscheduledRollbackBoundary, result.ExitCode); | ||
| 475 | } | ||
| 476 | } | ||
| 477 | } | ||
| 478 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs new file mode 100644 index 00000000..6d769bd6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs | |||
| @@ -0,0 +1,365 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using Example.Extension; | ||
| 9 | using WixBuildTools.TestSupport; | ||
| 10 | using WixToolset.Core.TestPackage; | ||
| 11 | using Xunit; | ||
| 12 | |||
| 13 | public class BundleManifestFixture | ||
| 14 | { | ||
| 15 | [Fact] | ||
| 16 | public void PopulatesBAManifestWithBootstrapperApplicationBundleCustomData() | ||
| 17 | { | ||
| 18 | var folder = TestData.Get(@"TestData"); | ||
| 19 | |||
| 20 | using (var fs = new DisposableFileSystem()) | ||
| 21 | { | ||
| 22 | var baseFolder = fs.GetFolder(); | ||
| 23 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 24 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 25 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 26 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 27 | |||
| 28 | var result = WixRunner.Execute(new[] | ||
| 29 | { | ||
| 30 | "build", | ||
| 31 | Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), | ||
| 32 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 33 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 34 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 35 | "-intermediateFolder", intermediateFolder, | ||
| 36 | "-o", bundlePath | ||
| 37 | }); | ||
| 38 | |||
| 39 | result.AssertSuccess(); | ||
| 40 | |||
| 41 | Assert.True(File.Exists(bundlePath)); | ||
| 42 | |||
| 43 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 44 | extractResult.AssertSuccess(); | ||
| 45 | |||
| 46 | var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTableBA"); | ||
| 47 | Assert.Equal(3, customElements.Count); | ||
| 48 | Assert.Equal("<BundleCustomTableBA Id='one' Column2='two' />", customElements[0].GetTestXml()); | ||
| 49 | Assert.Equal("<BundleCustomTableBA Id='>' Column2='<' />", customElements[1].GetTestXml()); | ||
| 50 | Assert.Equal("<BundleCustomTableBA Id='1' Column2='2' />", customElements[2].GetTestXml()); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | [Fact] | ||
| 55 | public void PopulatesBAManifestWithPackageInformation() | ||
| 56 | { | ||
| 57 | var folder = TestData.Get(@"TestData"); | ||
| 58 | |||
| 59 | using (var fs = new DisposableFileSystem()) | ||
| 60 | { | ||
| 61 | var baseFolder = fs.GetFolder(); | ||
| 62 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 63 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 64 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 65 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 66 | |||
| 67 | var result = WixRunner.Execute(false, new[] | ||
| 68 | { | ||
| 69 | "build", | ||
| 70 | Path.Combine(folder, "CustomPackageDescription", "CustomPackageDescription.wxs"), | ||
| 71 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 72 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 73 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 74 | "-intermediateFolder", intermediateFolder, | ||
| 75 | "-o", bundlePath | ||
| 76 | }); | ||
| 77 | |||
| 78 | result.AssertSuccess(); | ||
| 79 | |||
| 80 | Assert.True(File.Exists(bundlePath)); | ||
| 81 | |||
| 82 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 83 | extractResult.AssertSuccess(); | ||
| 84 | |||
| 85 | var packageElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPackageProperties"); | ||
| 86 | var ignoreAttributesByElementName = new Dictionary<string, List<string>> | ||
| 87 | { | ||
| 88 | { "WixPackageProperties", new List<string> { "DownloadSize", "PackageSize", "InstalledSize", "Version" } }, | ||
| 89 | }; | ||
| 90 | Assert.Equal(3, packageElements.Count); | ||
| 91 | Assert.Equal("<WixPackageProperties Package='burn.exe' Vital='yes' DisplayName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' DownloadSize='*' PackageSize='*' InstalledSize='*' PackageType='Exe' Permanent='yes' LogPathVariable='WixBundleLog_burn.exe' RollbackLogPathVariable='WixBundleRollbackLog_burn.exe' Compressed='yes' Version='*' Cache='keep' />", packageElements[0].GetTestXml(ignoreAttributesByElementName)); | ||
| 92 | Assert.Equal("<WixPackageProperties Package='RemotePayloadExe' Vital='yes' DisplayName='Override RemotePayload display name' Description='Override RemotePayload description' DownloadSize='1' PackageSize='1' InstalledSize='1' PackageType='Exe' Permanent='yes' LogPathVariable='WixBundleLog_RemotePayloadExe' RollbackLogPathVariable='WixBundleRollbackLog_RemotePayloadExe' Compressed='no' Version='1.0.0.0' Cache='keep' />", packageElements[1].GetTestXml()); | ||
| 93 | Assert.Equal("<WixPackageProperties Package='calc.exe' Vital='yes' DisplayName='Override harvested display name' Description='Override harvested description' DownloadSize='*' PackageSize='*' InstalledSize='*' PackageType='Exe' Permanent='yes' LogPathVariable='WixBundleLog_calc.exe' RollbackLogPathVariable='WixBundleRollbackLog_calc.exe' Compressed='yes' Version='*' Cache='keep' />", packageElements[2].GetTestXml(ignoreAttributesByElementName)); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | [Fact] | ||
| 98 | public void PopulatesBAManifestWithPayloadInformation() | ||
| 99 | { | ||
| 100 | var folder = TestData.Get(@"TestData"); | ||
| 101 | |||
| 102 | using (var fs = new DisposableFileSystem()) | ||
| 103 | { | ||
| 104 | var baseFolder = fs.GetFolder(); | ||
| 105 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 106 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 107 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 108 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 109 | |||
| 110 | var result = WixRunner.Execute(false, new[] | ||
| 111 | { | ||
| 112 | "build", | ||
| 113 | Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), | ||
| 114 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 115 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 116 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 117 | "-intermediateFolder", intermediateFolder, | ||
| 118 | "-o", bundlePath | ||
| 119 | }); | ||
| 120 | |||
| 121 | result.AssertSuccess(); | ||
| 122 | |||
| 123 | Assert.True(File.Exists(bundlePath)); | ||
| 124 | |||
| 125 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 126 | extractResult.AssertSuccess(); | ||
| 127 | |||
| 128 | var payloadElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties"); | ||
| 129 | var ignoreAttributesByElementName = new Dictionary<string, List<string>> | ||
| 130 | { | ||
| 131 | { "WixPayloadProperties", new List<string> { "Size" } }, | ||
| 132 | }; | ||
| 133 | Assert.Equal(4, payloadElements.Count); | ||
| 134 | Assert.Equal("<WixPayloadProperties Package='credwiz.exe' Payload='SourceFilePayload' Container='WixAttachedContainer' Name='SharedPayloadsBetweenPackages.wxs' Size='*' />", payloadElements[0].GetTestXml(ignoreAttributesByElementName)); | ||
| 135 | Assert.Equal("<WixPayloadProperties Package='credwiz.exe' Payload='credwiz.exe' Container='WixAttachedContainer' Name='credwiz.exe' Size='*' />", payloadElements[1].GetTestXml(ignoreAttributesByElementName)); | ||
| 136 | Assert.Equal("<WixPayloadProperties Package='cscript.exe' Payload='SourceFilePayload' Container='WixAttachedContainer' Name='SharedPayloadsBetweenPackages.wxs' Size='*' />", payloadElements[2].GetTestXml(ignoreAttributesByElementName)); | ||
| 137 | Assert.Equal("<WixPayloadProperties Package='cscript.exe' Payload='cscript.exe' Container='WixAttachedContainer' Name='cscript.exe' Size='*' />", payloadElements[3].GetTestXml(ignoreAttributesByElementName)); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | [Fact] | ||
| 142 | public void PopulatesBEManifestWithBundleExtensionBundleCustomData() | ||
| 143 | { | ||
| 144 | var folder = TestData.Get(@"TestData"); | ||
| 145 | |||
| 146 | using (var fs = new DisposableFileSystem()) | ||
| 147 | { | ||
| 148 | var baseFolder = fs.GetFolder(); | ||
| 149 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 150 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 151 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 152 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 153 | |||
| 154 | var result = WixRunner.Execute(new[] | ||
| 155 | { | ||
| 156 | "build", | ||
| 157 | Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), | ||
| 158 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 159 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 160 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 161 | "-intermediateFolder", intermediateFolder, | ||
| 162 | "-o", bundlePath | ||
| 163 | }); | ||
| 164 | |||
| 165 | result.AssertSuccess(); | ||
| 166 | |||
| 167 | Assert.True(File.Exists(bundlePath)); | ||
| 168 | |||
| 169 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 170 | extractResult.AssertSuccess(); | ||
| 171 | |||
| 172 | var customElements = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='CustomTableExtension']/be:BundleCustomTableBE"); | ||
| 173 | Assert.Equal(3, customElements.Count); | ||
| 174 | Assert.Equal("<BundleCustomTableBE Id='one' Column2='two' />", customElements[0].GetTestXml()); | ||
| 175 | Assert.Equal("<BundleCustomTableBE Id='>' Column2='<' />", customElements[1].GetTestXml()); | ||
| 176 | Assert.Equal("<BundleCustomTableBE Id='1' Column2='2' />", customElements[2].GetTestXml()); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | [Fact] | ||
| 181 | public void PopulatesManifestWithBundleExtension() | ||
| 182 | { | ||
| 183 | var folder = TestData.Get(@"TestData"); | ||
| 184 | |||
| 185 | using (var fs = new DisposableFileSystem()) | ||
| 186 | { | ||
| 187 | var baseFolder = fs.GetFolder(); | ||
| 188 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 189 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 190 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 191 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 192 | |||
| 193 | var result = WixRunner.Execute(new[] | ||
| 194 | { | ||
| 195 | "build", | ||
| 196 | Path.Combine(folder, "BundleExtension", "BundleExtension.wxs"), | ||
| 197 | Path.Combine(folder, "BundleExtension", "SimpleBundleExtension.wxs"), | ||
| 198 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 199 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 200 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 201 | "-intermediateFolder", intermediateFolder, | ||
| 202 | "-o", bundlePath | ||
| 203 | }); | ||
| 204 | |||
| 205 | result.AssertSuccess(); | ||
| 206 | |||
| 207 | Assert.True(File.Exists(bundlePath)); | ||
| 208 | |||
| 209 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 210 | extractResult.AssertSuccess(); | ||
| 211 | |||
| 212 | var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); | ||
| 213 | Assert.Equal(1, bundleExtensions.Count); | ||
| 214 | Assert.Equal("<BundleExtension Id='ExampleBext' EntryPayloadId='ExampleBext' />", bundleExtensions[0].GetTestXml()); | ||
| 215 | |||
| 216 | var bundleExtensionPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload[@Id='ExampleBext']"); | ||
| 217 | Assert.Equal(1, bundleExtensionPayloads.Count); | ||
| 218 | var ignored = new Dictionary<string, List<string>> | ||
| 219 | { | ||
| 220 | { "Payload", new List<string> { "FileSize", "Hash", "SourcePath" } }, | ||
| 221 | }; | ||
| 222 | Assert.Equal("<Payload Id='ExampleBext' FilePath='fakebext.dll' FileSize='*' Hash='*' Packaging='embedded' SourcePath='*' />", bundleExtensionPayloads[0].GetTestXml(ignored)); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | [Fact] | ||
| 227 | public void PopulatesManifestWithBundleExtensionSearches() | ||
| 228 | { | ||
| 229 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 230 | var folder = TestData.Get(@"TestData"); | ||
| 231 | |||
| 232 | using (var fs = new DisposableFileSystem()) | ||
| 233 | { | ||
| 234 | var baseFolder = fs.GetFolder(); | ||
| 235 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 236 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 237 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 238 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 239 | |||
| 240 | var result = WixRunner.Execute(new[] | ||
| 241 | { | ||
| 242 | "build", | ||
| 243 | Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"), | ||
| 244 | Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"), | ||
| 245 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 246 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 247 | "-ext", extensionPath, | ||
| 248 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 249 | "-intermediateFolder", intermediateFolder, | ||
| 250 | "-o", bundlePath | ||
| 251 | }); | ||
| 252 | |||
| 253 | result.AssertSuccess(); | ||
| 254 | |||
| 255 | Assert.True(File.Exists(bundlePath)); | ||
| 256 | |||
| 257 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 258 | extractResult.AssertSuccess(); | ||
| 259 | |||
| 260 | var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); | ||
| 261 | Assert.Equal(1, bundleExtensions.Count); | ||
| 262 | Assert.Equal("<BundleExtension Id='ExampleBundleExtension' EntryPayloadId='ExampleBundleExtension' />", bundleExtensions[0].GetTestXml()); | ||
| 263 | |||
| 264 | var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch"); | ||
| 265 | Assert.Equal(2, extensionSearches.Count); | ||
| 266 | Assert.Equal("<ExtensionSearch Id='ExampleSearchBar' Variable='SearchBar' Condition='WixBundleInstalled' ExtensionId='ExampleBundleExtension' />", extensionSearches[0].GetTestXml()); | ||
| 267 | Assert.Equal("<ExtensionSearch Id='ExampleSearchFoo' Variable='SearchFoo' ExtensionId='ExampleBundleExtension' />", extensionSearches[1].GetTestXml()); | ||
| 268 | |||
| 269 | var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']"); | ||
| 270 | Assert.Equal(1, bundleExtensionDatas.Count); | ||
| 271 | Assert.Equal("<BundleExtension Id='ExampleBundleExtension'>" + | ||
| 272 | "<ExampleSearch Id='ExampleSearchBar' SearchFor='Bar' />" + | ||
| 273 | "<ExampleSearch Id='ExampleSearchFoo' SearchFor='Foo' />" + | ||
| 274 | "</BundleExtension>", bundleExtensionDatas[0].GetTestXml()); | ||
| 275 | |||
| 276 | var exampleSearches = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']/be:ExampleSearch"); | ||
| 277 | Assert.Equal(2, exampleSearches.Count); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | [Fact] | ||
| 282 | public void PopulatesManifestWithExePackages() | ||
| 283 | { | ||
| 284 | var folder = TestData.Get(@"TestData"); | ||
| 285 | |||
| 286 | using (var fs = new DisposableFileSystem()) | ||
| 287 | { | ||
| 288 | var baseFolder = fs.GetFolder(); | ||
| 289 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 290 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 291 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 292 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 293 | |||
| 294 | var result = WixRunner.Execute(new[] | ||
| 295 | { | ||
| 296 | "build", | ||
| 297 | Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), | ||
| 298 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 299 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 300 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 301 | "-intermediateFolder", intermediateFolder, | ||
| 302 | "-o", bundlePath | ||
| 303 | }); | ||
| 304 | |||
| 305 | result.AssertSuccess(); | ||
| 306 | |||
| 307 | Assert.True(File.Exists(bundlePath)); | ||
| 308 | |||
| 309 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 310 | extractResult.AssertSuccess(); | ||
| 311 | |||
| 312 | var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); | ||
| 313 | var ignoreAttributesByElementName = new Dictionary<string, List<string>> | ||
| 314 | { | ||
| 315 | { "ExePackage", new List<string> { "CacheId", "InstallSize", "Size" } }, | ||
| 316 | }; | ||
| 317 | Assert.Equal(2, exePackageElements.Count); | ||
| 318 | Assert.Equal("<ExePackage Id='credwiz.exe' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='yes' Vital='yes' RollbackBoundaryForward='WixDefaultBoundary' LogPathVariable='WixBundleLog_credwiz.exe' RollbackLogPathVariable='WixBundleRollbackLog_credwiz.exe' DetectCondition='none' InstallArguments='' UninstallArguments='' RepairArguments='' Repairable='no'><PayloadRef Id='credwiz.exe' /><PayloadRef Id='SourceFilePayload' /></ExePackage>", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); | ||
| 319 | Assert.Equal("<ExePackage Id='cscript.exe' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='yes' Vital='yes' RollbackBoundaryBackward='WixDefaultBoundary' LogPathVariable='WixBundleLog_cscript.exe' RollbackLogPathVariable='WixBundleRollbackLog_cscript.exe' DetectCondition='none' InstallArguments='' UninstallArguments='' RepairArguments='' Repairable='no'><PayloadRef Id='cscript.exe' /><PayloadRef Id='SourceFilePayload' /></ExePackage>", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | [Fact] | ||
| 324 | public void PopulatesManifestWithSetVariables() | ||
| 325 | { | ||
| 326 | var folder = TestData.Get(@"TestData"); | ||
| 327 | |||
| 328 | using (var fs = new DisposableFileSystem()) | ||
| 329 | { | ||
| 330 | var baseFolder = fs.GetFolder(); | ||
| 331 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 332 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 333 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 334 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 335 | |||
| 336 | var result = WixRunner.Execute(new[] | ||
| 337 | { | ||
| 338 | "build", | ||
| 339 | Path.Combine(folder, "SetVariable", "Simple.wxs"), | ||
| 340 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 341 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 342 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 343 | "-intermediateFolder", intermediateFolder, | ||
| 344 | "-o", bundlePath | ||
| 345 | }); | ||
| 346 | |||
| 347 | result.AssertSuccess(); | ||
| 348 | |||
| 349 | Assert.True(File.Exists(bundlePath)); | ||
| 350 | |||
| 351 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 352 | extractResult.AssertSuccess(); | ||
| 353 | |||
| 354 | var setVariables = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:SetVariable"); | ||
| 355 | Assert.Equal(6, setVariables.Count); | ||
| 356 | Assert.Equal("<SetVariable Id='SetCoercedNumber' Variable='CoercedNumber' Value='2' Type='numeric' />", setVariables[0].GetTestXml()); | ||
| 357 | Assert.Equal("<SetVariable Id='SetCoercedString' Variable='CoercedString' Value='Bar' Type='string' />", setVariables[1].GetTestXml()); | ||
| 358 | Assert.Equal("<SetVariable Id='SetCoercedVersion' Variable='CoercedVersion' Value='v2.0' Type='version' />", setVariables[2].GetTestXml()); | ||
| 359 | Assert.Equal("<SetVariable Id='SetNeedsFormatting' Variable='NeedsFormatting' Value='[One] [Two] [Three]' Type='string' />", setVariables[3].GetTestXml()); | ||
| 360 | Assert.Equal("<SetVariable Id='SetVersionString' Variable='VersionString' Value='v1.0' Type='string' />", setVariables[4].GetTestXml()); | ||
| 361 | Assert.Equal("<SetVariable Id='SetUnset' Variable='Unset' Condition='VersionString = v2.0' />", setVariables[5].GetTestXml()); | ||
| 362 | } | ||
| 363 | } | ||
| 364 | } | ||
| 365 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs new file mode 100644 index 00000000..ad62dea6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs | |||
| @@ -0,0 +1,107 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using WixBuildTools.TestSupport; | ||
| 9 | using WixToolset.Core.TestPackage; | ||
| 10 | using Xunit; | ||
| 11 | |||
| 12 | public class CabFixture | ||
| 13 | { | ||
| 14 | [Fact] | ||
| 15 | public void CabinetFilesSequencedCorrectly() | ||
| 16 | { | ||
| 17 | var folder = TestData.Get(@"TestData\MultiFileCompressed"); | ||
| 18 | |||
| 19 | using (var fs = new DisposableFileSystem()) | ||
| 20 | { | ||
| 21 | var baseFolder = fs.GetFolder(); | ||
| 22 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 23 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 24 | var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); | ||
| 25 | |||
| 26 | var result = WixRunner.Execute(new[] | ||
| 27 | { | ||
| 28 | "build", | ||
| 29 | Path.Combine(folder, "Package.wxs"), | ||
| 30 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 31 | "-d", "MediaTemplateCompressionLevel", | ||
| 32 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 33 | "-bindpath", Path.Combine(folder, "data"), | ||
| 34 | "-intermediateFolder", intermediateFolder, | ||
| 35 | "-o", msiPath | ||
| 36 | }); | ||
| 37 | |||
| 38 | result.AssertSuccess(); | ||
| 39 | Assert.True(File.Exists(cabPath)); | ||
| 40 | |||
| 41 | var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); | ||
| 42 | var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); | ||
| 43 | |||
| 44 | Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray()); | ||
| 45 | Assert.Equal(new[] { "Notepad.exe", "test.txt" }, fileRows.Select(f => f.Name).ToArray()); | ||
| 46 | |||
| 47 | var files = Query.GetCabinetFiles(cabPath); | ||
| 48 | Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | [Fact(Skip = "Sequence number of file from merge module is 0 but should be 1.")] | ||
| 53 | public void CabinetFilesSequencedCorrectlyUsingMergeModule() | ||
| 54 | { | ||
| 55 | var folder = TestData.Get(@"TestData\SimpleMerge"); | ||
| 56 | |||
| 57 | using (var fs = new DisposableFileSystem()) | ||
| 58 | { | ||
| 59 | var baseFolder = fs.GetFolder(); | ||
| 60 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 61 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 62 | var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); | ||
| 63 | |||
| 64 | var result = WixRunner.Execute(new[] | ||
| 65 | { | ||
| 66 | "build", | ||
| 67 | Path.Combine(folder, "Package.wxs"), | ||
| 68 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 69 | "-bindpath", Path.Combine(folder, ".data"), | ||
| 70 | "-intermediateFolder", intermediateFolder, | ||
| 71 | "-o", msiPath | ||
| 72 | }); | ||
| 73 | |||
| 74 | result.AssertSuccess(); | ||
| 75 | Assert.True(File.Exists(cabPath)); | ||
| 76 | |||
| 77 | var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); | ||
| 78 | var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); | ||
| 79 | |||
| 80 | Assert.Equal(new[] { 1 }, fileRows.Select(f => f.Sequence).ToArray()); | ||
| 81 | Assert.Equal(new[] { "test.txt" }, fileRows.Select(f => f.Name).ToArray()); | ||
| 82 | |||
| 83 | var files = Query.GetCabinetFiles(cabPath); | ||
| 84 | Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | private class FileRow | ||
| 89 | { | ||
| 90 | public FileRow(string row) | ||
| 91 | { | ||
| 92 | row = row.Substring("File:".Length); | ||
| 93 | |||
| 94 | var split = row.Split('\t'); | ||
| 95 | this.Id = split[0]; | ||
| 96 | this.Name = split[2]; | ||
| 97 | this.Sequence = Convert.ToInt32(split[7]); | ||
| 98 | } | ||
| 99 | |||
| 100 | public string Id { get; set; } | ||
| 101 | |||
| 102 | public string Name { get; set; } | ||
| 103 | |||
| 104 | public int Sequence { get; set; } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs new file mode 100644 index 00000000..d24ba08c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using WixBuildTools.TestSupport; | ||
| 9 | using WixToolset.Core.TestPackage; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using Xunit; | ||
| 12 | |||
| 13 | public class ComponentFixture | ||
| 14 | { | ||
| 15 | [Fact] | ||
| 16 | public void CanDetectDuplicateComponentGuids() | ||
| 17 | { | ||
| 18 | var folder = TestData.Get(@"TestData"); | ||
| 19 | |||
| 20 | using (var fs = new DisposableFileSystem()) | ||
| 21 | { | ||
| 22 | var baseFolder = fs.GetFolder(); | ||
| 23 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 24 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 25 | |||
| 26 | var result = WixRunner.Execute(new[] | ||
| 27 | { | ||
| 28 | "build", | ||
| 29 | Path.Combine(folder, "Component", "GuidCollision.wxs"), | ||
| 30 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 31 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 32 | "-intermediateFolder", intermediateFolder, | ||
| 33 | "-o", msiPath | ||
| 34 | }); | ||
| 35 | |||
| 36 | var errors = result.Messages.Where(m => m.Level == MessageLevel.Error); | ||
| 37 | Array.Equals(new[] | ||
| 38 | { | ||
| 39 | 369, | ||
| 40 | 369 | ||
| 41 | }, errors.Select(e => e.Id).ToArray()); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs new file mode 100644 index 00000000..dd381dfe --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs | |||
| @@ -0,0 +1,385 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Xml; | ||
| 10 | using WixBuildTools.TestSupport; | ||
| 11 | using WixToolset.Core; | ||
| 12 | using WixToolset.Core.Burn; | ||
| 13 | using WixToolset.Core.TestPackage; | ||
| 14 | using Xunit; | ||
| 15 | |||
| 16 | public class ContainerFixture | ||
| 17 | { | ||
| 18 | [Fact(Skip = "Test demonstrates failure")] | ||
| 19 | public void CanBuildWithCustomAttachedContainer() | ||
| 20 | { | ||
| 21 | var folder = TestData.Get(@"TestData"); | ||
| 22 | |||
| 23 | using (var fs = new DisposableFileSystem()) | ||
| 24 | { | ||
| 25 | var baseFolder = fs.GetFolder(); | ||
| 26 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 27 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 28 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 29 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 30 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 31 | |||
| 32 | this.BuildMsis(folder, intermediateFolder, binFolder, buildToSubfolder: true); | ||
| 33 | |||
| 34 | var result = WixRunner.Execute(new[] | ||
| 35 | { | ||
| 36 | "build", | ||
| 37 | Path.Combine(folder, "Container", "HarvestIntoAttachedContainer.wxs"), | ||
| 38 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 39 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 40 | "-bindpath", binFolder, | ||
| 41 | "-intermediateFolder", intermediateFolder, | ||
| 42 | "-o", bundlePath | ||
| 43 | }); | ||
| 44 | |||
| 45 | result.AssertSuccess(); | ||
| 46 | |||
| 47 | Assert.True(File.Exists(bundlePath)); | ||
| 48 | |||
| 49 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 50 | extractResult.AssertSuccess(); | ||
| 51 | |||
| 52 | var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); | ||
| 53 | Assert.Equal(4, payloads.Count); | ||
| 54 | var ignoreAttributes = new Dictionary<string, List<string>> { { "Payload", new List<string> { "FileSize", "Hash" } } }; | ||
| 55 | Assert.Equal(@"<Payload Id='FirstX64' FilePath='FirstX64\FirstX64.msi' FileSize='*' Hash='*' DownloadUrl='http://example.com//FirstX64/FirstX64/FirstX64.msi' Packaging='embedded' SourcePath='a0' Container='BundlePackages' />", payloads[0].GetTestXml(ignoreAttributes)); | ||
| 56 | Assert.Equal(@"<Payload Id='FirstX86.msi' FilePath='FirstX86\FirstX86.msi' FileSize='*' Hash='*' DownloadUrl='http://example.com//FirstX86.msi/FirstX86/FirstX86.msi' Packaging='embedded' SourcePath='a1' Container='BundlePackages' />", payloads[1].GetTestXml(ignoreAttributes)); | ||
| 57 | Assert.Equal(@"<Payload Id='fk1m38Cf9RZ2Bx_ipinRY6BftelU' FilePath='FirstX86\PFiles\MsiPackage\test.txt' FileSize='*' Hash='*' DownloadUrl='http://example.com/FirstX86/fk1m38Cf9RZ2Bx_ipinRY6BftelU/FirstX86/PFiles/MsiPackage/test.txt' Packaging='embedded' SourcePath='a2' Container='BundlePackages' />", payloads[2].GetTestXml(ignoreAttributes)); | ||
| 58 | Assert.Equal(@"<Payload Id='ff2L_N_DLQ.nSUi.l8LxG14gd2V4' FilePath='FirstX64\PFiles\MsiPackage\test.txt' FileSize='*' Hash='*' DownloadUrl='http://example.com/FirstX64/ff2L_N_DLQ.nSUi.l8LxG14gd2V4/FirstX64/PFiles/MsiPackage/test.txt' Packaging='embedded' SourcePath='a3' Container='BundlePackages' />", payloads[3].GetTestXml(ignoreAttributes)); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | [Fact] | ||
| 63 | public void HarvestedPayloadsArePutInCorrectContainer() | ||
| 64 | { | ||
| 65 | var folder = TestData.Get(@"TestData"); | ||
| 66 | |||
| 67 | using (var fs = new DisposableFileSystem()) | ||
| 68 | { | ||
| 69 | var baseFolder = fs.GetFolder(); | ||
| 70 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 71 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 72 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 73 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 74 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 75 | |||
| 76 | this.BuildMsis(folder, intermediateFolder, binFolder); | ||
| 77 | |||
| 78 | var result = WixRunner.Execute(new[] | ||
| 79 | { | ||
| 80 | "build", | ||
| 81 | Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), | ||
| 82 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 83 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 84 | "-bindpath", binFolder, | ||
| 85 | "-intermediateFolder", intermediateFolder, | ||
| 86 | "-o", bundlePath | ||
| 87 | }); | ||
| 88 | |||
| 89 | result.AssertSuccess(); | ||
| 90 | |||
| 91 | Assert.True(File.Exists(bundlePath)); | ||
| 92 | |||
| 93 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 94 | extractResult.AssertSuccess(); | ||
| 95 | |||
| 96 | var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); | ||
| 97 | Assert.Equal(4, payloads.Count); | ||
| 98 | var ignoreAttributes = new Dictionary<string, List<string>> { { "Payload", new List<string> { "FileSize", "Hash" } } }; | ||
| 99 | Assert.Equal(@"<Payload Id='FirstX86.msi' FilePath='FirstX86.msi' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a0' Container='WixAttachedContainer' />", payloads[0].GetTestXml(ignoreAttributes)); | ||
| 100 | Assert.Equal(@"<Payload Id='FirstX64.msi' FilePath='FirstX64.msi' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a1' Container='FirstX64' />", payloads[1].GetTestXml(ignoreAttributes)); | ||
| 101 | Assert.Equal(@"<Payload Id='fk1m38Cf9RZ2Bx_ipinRY6BftelU' FilePath='PFiles\MsiPackage\test.txt' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a2' Container='WixAttachedContainer' />", payloads[2].GetTestXml(ignoreAttributes)); | ||
| 102 | Assert.Equal(@"<Payload Id='fC0n41rZK8oW3JK8LzHu6AT3CjdQ' FilePath='PFiles\MsiPackage\test.txt' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a3' Container='FirstX64' />", payloads[3].GetTestXml(ignoreAttributes)); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | [Fact] | ||
| 107 | public void HarvestedPayloadsArePutInCorrectPackage() | ||
| 108 | { | ||
| 109 | var folder = TestData.Get(@"TestData"); | ||
| 110 | |||
| 111 | using (var fs = new DisposableFileSystem()) | ||
| 112 | { | ||
| 113 | var baseFolder = fs.GetFolder(); | ||
| 114 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 115 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 116 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 117 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 118 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 119 | |||
| 120 | this.BuildMsis(folder, intermediateFolder, binFolder); | ||
| 121 | |||
| 122 | var result = WixRunner.Execute(new[] | ||
| 123 | { | ||
| 124 | "build", | ||
| 125 | Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), | ||
| 126 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 127 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 128 | "-bindpath", binFolder, | ||
| 129 | "-intermediateFolder", intermediateFolder, | ||
| 130 | "-o", bundlePath | ||
| 131 | }); | ||
| 132 | |||
| 133 | result.AssertSuccess(); | ||
| 134 | |||
| 135 | Assert.True(File.Exists(bundlePath)); | ||
| 136 | |||
| 137 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 138 | extractResult.AssertSuccess(); | ||
| 139 | |||
| 140 | var ignoreAttributes = new Dictionary<string, List<string>> | ||
| 141 | { | ||
| 142 | { "MsiPackage", new List<string> { "CacheId", "InstallSize", "Size", "ProductCode" } }, | ||
| 143 | { "Provides", new List<string> { "Key" } }, | ||
| 144 | }; | ||
| 145 | var msiPackages = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage") | ||
| 146 | .Cast<XmlElement>() | ||
| 147 | .Select(e => e.GetTestXml(ignoreAttributes)) | ||
| 148 | .ToArray(); | ||
| 149 | WixAssert.CompareLineByLine(new string[] | ||
| 150 | { | ||
| 151 | "<MsiPackage Id='FirstX86.msi' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='no' Vital='yes' RollbackBoundaryForward='WixDefaultBoundary' LogPathVariable='WixBundleLog_FirstX86.msi' RollbackLogPathVariable='WixBundleRollbackLog_FirstX86.msi' ProductCode='*' Language='1033' Version='1.0.0.0' UpgradeCode='{12E4699F-E774-4D05-8A01-5BDD41BBA127}'>" + | ||
| 152 | "<MsiProperty Id='ARPSYSTEMCOMPONENT' Value='1' />" + | ||
| 153 | "<Provides Key='*' Version='1.0.0.0' DisplayName='MsiPackage' />" + | ||
| 154 | "<RelatedPackage Id='{12E4699F-E774-4D05-8A01-5BDD41BBA127}' MaxVersion='1.0.0.0' MaxInclusive='no' OnlyDetect='no' LangInclusive='no'><Language Id='1033' /></RelatedPackage>" + | ||
| 155 | "<RelatedPackage Id='{12E4699F-E774-4D05-8A01-5BDD41BBA127}' MinVersion='1.0.0.0' MinInclusive='no' OnlyDetect='yes' LangInclusive='no'><Language Id='1033' /></RelatedPackage>" + | ||
| 156 | "<PayloadRef Id='FirstX86.msi' />" + | ||
| 157 | "<PayloadRef Id='fk1m38Cf9RZ2Bx_ipinRY6BftelU' />" + | ||
| 158 | "</MsiPackage>", | ||
| 159 | "<MsiPackage Id='FirstX64.msi' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='no' Vital='yes' RollbackBoundaryBackward='WixDefaultBoundary' LogPathVariable='WixBundleLog_FirstX64.msi' RollbackLogPathVariable='WixBundleRollbackLog_FirstX64.msi' ProductCode='*' Language='1033' Version='1.0.0.0' UpgradeCode='{12E4699F-E774-4D05-8A01-5BDD41BBA127}'>" + | ||
| 160 | "<MsiProperty Id='ARPSYSTEMCOMPONENT' Value='1' />" + | ||
| 161 | "<Provides Key='*' Version='1.0.0.0' DisplayName='MsiPackage' />" + | ||
| 162 | "<RelatedPackage Id='{12E4699F-E774-4D05-8A01-5BDD41BBA127}' MaxVersion='1.0.0.0' MaxInclusive='no' OnlyDetect='no' LangInclusive='no'><Language Id='1033' /></RelatedPackage>" + | ||
| 163 | "<RelatedPackage Id='{12E4699F-E774-4D05-8A01-5BDD41BBA127}' MinVersion='1.0.0.0' MinInclusive='no' OnlyDetect='yes' LangInclusive='no'><Language Id='1033' /></RelatedPackage>" + | ||
| 164 | "<PayloadRef Id='FirstX64.msi' />" + | ||
| 165 | "<PayloadRef Id='fC0n41rZK8oW3JK8LzHu6AT3CjdQ' />" + | ||
| 166 | "</MsiPackage>", | ||
| 167 | }, msiPackages); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | [Fact] | ||
| 172 | public void LayoutPayloadIsPutInContainer() | ||
| 173 | { | ||
| 174 | var folder = TestData.Get(@"TestData"); | ||
| 175 | |||
| 176 | using (var fs = new DisposableFileSystem()) | ||
| 177 | { | ||
| 178 | var baseFolder = fs.GetFolder(); | ||
| 179 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 180 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 181 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 182 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 183 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 184 | |||
| 185 | this.BuildMsis(folder, intermediateFolder, binFolder); | ||
| 186 | |||
| 187 | var result = WixRunner.Execute(false, new[] | ||
| 188 | { | ||
| 189 | "build", | ||
| 190 | Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), | ||
| 191 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 192 | "-bindpath", binFolder, | ||
| 193 | "-intermediateFolder", intermediateFolder, | ||
| 194 | "-o", bundlePath | ||
| 195 | }); | ||
| 196 | |||
| 197 | WixAssert.CompareLineByLine(new string[] | ||
| 198 | { | ||
| 199 | "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", | ||
| 200 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 201 | result.AssertSuccess(); | ||
| 202 | |||
| 203 | Assert.True(File.Exists(bundlePath)); | ||
| 204 | |||
| 205 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 206 | extractResult.AssertSuccess(); | ||
| 207 | |||
| 208 | var ignoreAttributes = new Dictionary<string, List<string>> { { "Payload", new List<string> { "FileSize", "Hash" } } }; | ||
| 209 | var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") | ||
| 210 | .Cast<XmlElement>() | ||
| 211 | .Select(e => e.GetTestXml(ignoreAttributes)) | ||
| 212 | .ToArray(); | ||
| 213 | WixAssert.CompareLineByLine(new string[] | ||
| 214 | { | ||
| 215 | "<Payload Id='SharedPayload' FilePath='LayoutPayloadInContainer.wxs' FileSize='*' Hash='*' LayoutOnly='yes' Packaging='embedded' SourcePath='a1' Container='FirstX64' />", | ||
| 216 | }, payloads); | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | [Fact] | ||
| 221 | public void MultipleAttachedContainersAreNotCurrentlySupported() | ||
| 222 | { | ||
| 223 | var folder = TestData.Get(@"TestData"); | ||
| 224 | |||
| 225 | using (var fs = new DisposableFileSystem()) | ||
| 226 | { | ||
| 227 | var baseFolder = fs.GetFolder(); | ||
| 228 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 229 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 230 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 231 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 232 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 233 | |||
| 234 | this.BuildMsis(folder, intermediateFolder, binFolder); | ||
| 235 | |||
| 236 | var result = WixRunner.Execute(new[] | ||
| 237 | { | ||
| 238 | "build", | ||
| 239 | Path.Combine(folder, "Container", "MultipleAttachedContainers.wxs"), | ||
| 240 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 241 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 242 | "-bindpath", binFolder, | ||
| 243 | "-intermediateFolder", intermediateFolder, | ||
| 244 | "-o", bundlePath | ||
| 245 | }); | ||
| 246 | |||
| 247 | Assert.Equal((int)BurnBackendErrors.Ids.MultipleAttachedContainersUnsupported, result.ExitCode); | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | [Fact] | ||
| 252 | public void PayloadIsNotPutInMultipleContainers() | ||
| 253 | { | ||
| 254 | var folder = TestData.Get(@"TestData"); | ||
| 255 | |||
| 256 | using (var fs = new DisposableFileSystem()) | ||
| 257 | { | ||
| 258 | var baseFolder = fs.GetFolder(); | ||
| 259 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 260 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 261 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 262 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 263 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 264 | |||
| 265 | this.BuildMsis(folder, intermediateFolder, binFolder); | ||
| 266 | |||
| 267 | var result = WixRunner.Execute(false, new[] | ||
| 268 | { | ||
| 269 | "build", | ||
| 270 | Path.Combine(folder, "Container", "PayloadInMultipleContainers.wxs"), | ||
| 271 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 272 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 273 | "-bindpath", binFolder, | ||
| 274 | "-intermediateFolder", intermediateFolder, | ||
| 275 | "-o", bundlePath | ||
| 276 | }); | ||
| 277 | |||
| 278 | WixAssert.CompareLineByLine(new string[] | ||
| 279 | { | ||
| 280 | "The Payload 'SharedPayload' can't be added to Container 'FirstX64' because it was already added to Container 'FirstX86'.", | ||
| 281 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 282 | result.AssertSuccess(); | ||
| 283 | |||
| 284 | Assert.True(File.Exists(bundlePath)); | ||
| 285 | |||
| 286 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 287 | extractResult.AssertSuccess(); | ||
| 288 | |||
| 289 | var ignoreAttributes = new Dictionary<string, List<string>> { { "Payload", new List<string> { "FileSize", "Hash" } } }; | ||
| 290 | var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") | ||
| 291 | .Cast<XmlElement>() | ||
| 292 | .Select(e => e.GetTestXml(ignoreAttributes)) | ||
| 293 | .ToArray(); | ||
| 294 | WixAssert.CompareLineByLine(new string[] | ||
| 295 | { | ||
| 296 | "<Payload Id='SharedPayload' FilePath='PayloadInMultipleContainers.wxs' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a2' Container='FirstX86' />", | ||
| 297 | }, payloads); | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 301 | [Fact] | ||
| 302 | public void PopulatesBAManifestWithLayoutOnlyPayloads() | ||
| 303 | { | ||
| 304 | var folder = TestData.Get(@"TestData"); | ||
| 305 | |||
| 306 | using (var fs = new DisposableFileSystem()) | ||
| 307 | { | ||
| 308 | var baseFolder = fs.GetFolder(); | ||
| 309 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 310 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 311 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 312 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 313 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 314 | |||
| 315 | this.BuildMsis(folder, intermediateFolder, binFolder); | ||
| 316 | |||
| 317 | var result = WixRunner.Execute(false, new[] | ||
| 318 | { | ||
| 319 | "build", | ||
| 320 | Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), | ||
| 321 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 322 | "-bindpath", binFolder, | ||
| 323 | "-intermediateFolder", intermediateFolder, | ||
| 324 | "-o", bundlePath | ||
| 325 | }); | ||
| 326 | |||
| 327 | WixAssert.CompareLineByLine(new string[] | ||
| 328 | { | ||
| 329 | "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", | ||
| 330 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 331 | result.AssertSuccess(); | ||
| 332 | |||
| 333 | Assert.True(File.Exists(bundlePath)); | ||
| 334 | |||
| 335 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 336 | extractResult.AssertSuccess(); | ||
| 337 | |||
| 338 | var ignoreAttributesByElementName = new Dictionary<string, List<string>> | ||
| 339 | { | ||
| 340 | { "WixPayloadProperties", new List<string> { "Size" } }, | ||
| 341 | }; | ||
| 342 | var payloads = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties") | ||
| 343 | .Cast<XmlElement>() | ||
| 344 | .Select(e => e.GetTestXml(ignoreAttributesByElementName)) | ||
| 345 | .ToArray(); | ||
| 346 | WixAssert.CompareLineByLine(new string[] | ||
| 347 | { | ||
| 348 | "<WixPayloadProperties Package='FirstX64.msi' Payload='FirstX64.msi' Container='FirstX64' Name='FirstX64.msi' Size='*' />", | ||
| 349 | "<WixPayloadProperties Package='FirstX64.msi' Payload='SharedPayload' Container='FirstX64' Name='LayoutPayloadInContainer.wxs' Size='*' />", | ||
| 350 | "<WixPayloadProperties Package='FirstX64.msi' Payload='fC0n41rZK8oW3JK8LzHu6AT3CjdQ' Container='FirstX64' Name='PFiles\\MsiPackage\\test.txt' Size='*' />", | ||
| 351 | "<WixPayloadProperties Payload='SharedPayload' Container='FirstX64' Name='LayoutPayloadInContainer.wxs' Size='*' />", | ||
| 352 | }, payloads); | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | private void BuildMsis(string folder, string intermediateFolder, string binFolder, bool buildToSubfolder = false) | ||
| 357 | { | ||
| 358 | var result = WixRunner.Execute(new[] | ||
| 359 | { | ||
| 360 | "build", | ||
| 361 | Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), | ||
| 362 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 363 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 364 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 365 | "-intermediateFolder", intermediateFolder, | ||
| 366 | "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX86" : ".", "FirstX86.msi"), | ||
| 367 | }); | ||
| 368 | |||
| 369 | result.AssertSuccess(); | ||
| 370 | |||
| 371 | result = WixRunner.Execute(new[] | ||
| 372 | { | ||
| 373 | "build", | ||
| 374 | Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), | ||
| 375 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 376 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 377 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 378 | "-intermediateFolder", intermediateFolder, | ||
| 379 | "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX64" : ".", "FirstX64.msi"), | ||
| 380 | }); | ||
| 381 | |||
| 382 | result.AssertSuccess(); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs new file mode 100644 index 00000000..c6fa602b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Linq; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core.TestPackage; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using Xunit; | ||
| 12 | |||
| 13 | public class CopyFileFixture | ||
| 14 | { | ||
| 15 | [Fact] | ||
| 16 | public void CanBuildCopyFile() | ||
| 17 | { | ||
| 18 | var folder = TestData.Get(@"TestData"); | ||
| 19 | |||
| 20 | using (var fs = new DisposableFileSystem()) | ||
| 21 | { | ||
| 22 | var baseFolder = fs.GetFolder(); | ||
| 23 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 24 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 25 | |||
| 26 | var result = WixRunner.Execute(new[] | ||
| 27 | { | ||
| 28 | "build", | ||
| 29 | Path.Combine(folder, "CopyFile", "CopyFile.wxs"), | ||
| 30 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 31 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 32 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 33 | "-intermediateFolder", intermediateFolder, | ||
| 34 | "-o", msiPath | ||
| 35 | }); | ||
| 36 | |||
| 37 | result.AssertSuccess(); | ||
| 38 | |||
| 39 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 40 | var section = intermediate.Sections.Single(); | ||
| 41 | var copyFileSymbol = section.Symbols.OfType<MoveFileSymbol>().Single(); | ||
| 42 | Assert.Equal("MoveText", copyFileSymbol.Id.Id); | ||
| 43 | Assert.True(copyFileSymbol.Delete); | ||
| 44 | Assert.Equal("OtherFolder", copyFileSymbol.DestFolder); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs new file mode 100644 index 00000000..636b86a6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs | |||
| @@ -0,0 +1,169 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Linq; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core.TestPackage; | ||
| 9 | using Xunit; | ||
| 10 | |||
| 11 | public class CustomActionFixture | ||
| 12 | { | ||
| 13 | [Fact] | ||
| 14 | public void CanDetectCustomActionCycle() | ||
| 15 | { | ||
| 16 | var folder = TestData.Get(@"TestData"); | ||
| 17 | |||
| 18 | using (var fs = new DisposableFileSystem()) | ||
| 19 | { | ||
| 20 | var baseFolder = fs.GetFolder(); | ||
| 21 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 22 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 23 | |||
| 24 | var result = WixRunner.Execute(new[] | ||
| 25 | { | ||
| 26 | "build", | ||
| 27 | Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"), | ||
| 28 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 29 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 30 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 31 | "-intermediateFolder", intermediateFolder, | ||
| 32 | "-o", msiPath | ||
| 33 | }); | ||
| 34 | |||
| 35 | Assert.Equal(176, result.ExitCode); | ||
| 36 | Assert.Equal("The InstallExecuteSequence table contains an action 'Action1' that is scheduled to come before or after action 'Action3', which is also scheduled to come before or after action 'Action1'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | [Fact] | ||
| 41 | public void CanDetectCustomActionCycleWithTail() | ||
| 42 | { | ||
| 43 | var folder = TestData.Get(@"TestData"); | ||
| 44 | |||
| 45 | using (var fs = new DisposableFileSystem()) | ||
| 46 | { | ||
| 47 | var baseFolder = fs.GetFolder(); | ||
| 48 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 49 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 50 | |||
| 51 | var result = WixRunner.Execute(new[] | ||
| 52 | { | ||
| 53 | "build", | ||
| 54 | Path.Combine(folder, "CustomAction", "CustomActionCycleWithTail.wxs"), | ||
| 55 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 56 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 57 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 58 | "-intermediateFolder", intermediateFolder, | ||
| 59 | "-o", msiPath | ||
| 60 | }); | ||
| 61 | |||
| 62 | Assert.Equal(176, result.ExitCode); | ||
| 63 | Assert.Equal("The InstallExecuteSequence table contains an action 'Action2' that is scheduled to come before or after action 'Action4', which is also scheduled to come before or after action 'Action2'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | [Fact] | ||
| 68 | public void PopulatesCustomActionTable() | ||
| 69 | { | ||
| 70 | var folder = TestData.Get(@"TestData"); | ||
| 71 | |||
| 72 | using (var fs = new DisposableFileSystem()) | ||
| 73 | { | ||
| 74 | var baseFolder = fs.GetFolder(); | ||
| 75 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 76 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 77 | |||
| 78 | var result = WixRunner.Execute(new[] | ||
| 79 | { | ||
| 80 | "build", | ||
| 81 | Path.Combine(folder, "CustomAction", "UnscheduledCustomAction.wxs"), | ||
| 82 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 83 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 84 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 85 | "-intermediateFolder", intermediateFolder, | ||
| 86 | "-o", msiPath | ||
| 87 | }); | ||
| 88 | |||
| 89 | result.AssertSuccess(); | ||
| 90 | |||
| 91 | Assert.True(File.Exists(msiPath)); | ||
| 92 | var results = Query.QueryDatabase(msiPath, new[] { | ||
| 93 | "ActionText", | ||
| 94 | "AdminExecuteSequence", | ||
| 95 | "AdminUISequence", | ||
| 96 | "AdvtExecuteSequence", | ||
| 97 | "Binary", | ||
| 98 | "CustomAction", | ||
| 99 | "InstallExecuteSequence", | ||
| 100 | "InstallUISequence", | ||
| 101 | "Property", | ||
| 102 | }).Where(x => !x.StartsWith("Property:") || x.StartsWith("Property:MsiHiddenProperties\t")).ToArray(); | ||
| 103 | Assert.Equal(new[] | ||
| 104 | { | ||
| 105 | "ActionText:CustomAction2\tProgess2Text\t", | ||
| 106 | "AdminExecuteSequence:CostFinalize\t\t1000", | ||
| 107 | "AdminExecuteSequence:CostInitialize\t\t800", | ||
| 108 | "AdminExecuteSequence:CustomAction2\t\t801", | ||
| 109 | "AdminExecuteSequence:FileCost\t\t900", | ||
| 110 | "AdminExecuteSequence:InstallAdminPackage\t\t3900", | ||
| 111 | "AdminExecuteSequence:InstallFiles\t\t4000", | ||
| 112 | "AdminExecuteSequence:InstallFinalize\t\t6600", | ||
| 113 | "AdminExecuteSequence:InstallInitialize\t\t1500", | ||
| 114 | "AdminExecuteSequence:InstallValidate\t\t1400", | ||
| 115 | "AdminUISequence:CostFinalize\t\t1000", | ||
| 116 | "AdminUISequence:CostInitialize\t\t800", | ||
| 117 | "AdminUISequence:CustomAction2\t\t801", | ||
| 118 | "AdminUISequence:ExecuteAction\t\t1300", | ||
| 119 | "AdminUISequence:FileCost\t\t900", | ||
| 120 | "AdvtExecuteSequence:CostFinalize\t\t1000", | ||
| 121 | "AdvtExecuteSequence:CostInitialize\t\t800", | ||
| 122 | "AdvtExecuteSequence:CustomAction2\t\t801", | ||
| 123 | "AdvtExecuteSequence:InstallFinalize\t\t6600", | ||
| 124 | "AdvtExecuteSequence:InstallInitialize\t\t1500", | ||
| 125 | "AdvtExecuteSequence:InstallValidate\t\t1400", | ||
| 126 | "AdvtExecuteSequence:PublishFeatures\t\t6300", | ||
| 127 | "AdvtExecuteSequence:PublishProduct\t\t6400", | ||
| 128 | "Binary:Binary1\t[Binary data]", | ||
| 129 | "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t", | ||
| 130 | "CustomAction:CustomAction2\t51\tTestAdvtExecuteSequenceProperty\t1\t", | ||
| 131 | "CustomAction:CustomActionWithHiddenTarget\t9217\tBinary1\tInvalidEntryPoint\t", | ||
| 132 | "CustomAction:DiscardOptimismAllBeingsWhoProceed\t19\t\tAbandon hope all ye who enter here.\t", | ||
| 133 | "InstallExecuteSequence:CostFinalize\t\t1000", | ||
| 134 | "InstallExecuteSequence:CostInitialize\t\t800", | ||
| 135 | "InstallExecuteSequence:CreateFolders\t\t3700", | ||
| 136 | "InstallExecuteSequence:CustomAction2\t\t801", | ||
| 137 | "InstallExecuteSequence:FileCost\t\t900", | ||
| 138 | "InstallExecuteSequence:FindRelatedProducts\t\t25", | ||
| 139 | "InstallExecuteSequence:InstallFiles\t\t4000", | ||
| 140 | "InstallExecuteSequence:InstallFinalize\t\t6600", | ||
| 141 | "InstallExecuteSequence:InstallInitialize\t\t1500", | ||
| 142 | "InstallExecuteSequence:InstallValidate\t\t1400", | ||
| 143 | "InstallExecuteSequence:LaunchConditions\t\t100", | ||
| 144 | "InstallExecuteSequence:MigrateFeatureStates\t\t1200", | ||
| 145 | "InstallExecuteSequence:ProcessComponents\t\t1600", | ||
| 146 | "InstallExecuteSequence:PublishFeatures\t\t6300", | ||
| 147 | "InstallExecuteSequence:PublishProduct\t\t6400", | ||
| 148 | "InstallExecuteSequence:RegisterProduct\t\t6100", | ||
| 149 | "InstallExecuteSequence:RegisterUser\t\t6000", | ||
| 150 | "InstallExecuteSequence:RemoveExistingProducts\t\t1401", | ||
| 151 | "InstallExecuteSequence:RemoveFiles\t\t3500", | ||
| 152 | "InstallExecuteSequence:RemoveFolders\t\t3600", | ||
| 153 | "InstallExecuteSequence:UnpublishFeatures\t\t1800", | ||
| 154 | "InstallExecuteSequence:ValidateProductID\t\t700", | ||
| 155 | "InstallUISequence:CostFinalize\t\t1000", | ||
| 156 | "InstallUISequence:CostInitialize\t\t800", | ||
| 157 | "InstallUISequence:CustomAction2\t\t801", | ||
| 158 | "InstallUISequence:ExecuteAction\t\t1300", | ||
| 159 | "InstallUISequence:FileCost\t\t900", | ||
| 160 | "InstallUISequence:FindRelatedProducts\t\t25", | ||
| 161 | "InstallUISequence:LaunchConditions\t\t100", | ||
| 162 | "InstallUISequence:MigrateFeatureStates\t\t1200", | ||
| 163 | "InstallUISequence:ValidateProductID\t\t700", | ||
| 164 | "Property:MsiHiddenProperties\tCustomActionWithHiddenTarget", | ||
| 165 | }, results); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs new file mode 100644 index 00000000..ee93b03a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs | |||
| @@ -0,0 +1,234 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Xml.Linq; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core.TestPackage; | ||
| 9 | using Xunit; | ||
| 10 | |||
| 11 | public class CustomTableFixture | ||
| 12 | { | ||
| 13 | [Fact] | ||
| 14 | public void PopulatesCustomTable1() | ||
| 15 | { | ||
| 16 | var folder = TestData.Get(@"TestData"); | ||
| 17 | |||
| 18 | using (var fs = new DisposableFileSystem()) | ||
| 19 | { | ||
| 20 | var baseFolder = fs.GetFolder(); | ||
| 21 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 22 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 23 | |||
| 24 | var result = WixRunner.Execute(new[] | ||
| 25 | { | ||
| 26 | "build", | ||
| 27 | Path.Combine(folder, "CustomTable", "CustomTable.wxs"), | ||
| 28 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 29 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 30 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 31 | "-intermediateFolder", intermediateFolder, | ||
| 32 | "-o", msiPath | ||
| 33 | }); | ||
| 34 | |||
| 35 | result.AssertSuccess(); | ||
| 36 | |||
| 37 | Assert.True(File.Exists(msiPath)); | ||
| 38 | var results = Query.QueryDatabase(msiPath, new[] { "CustomTable1" }); | ||
| 39 | Assert.Equal(new[] | ||
| 40 | { | ||
| 41 | "CustomTable1:Row1\ttest.txt", | ||
| 42 | "CustomTable1:Row2\ttest.txt", | ||
| 43 | }, results); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | [Fact] | ||
| 48 | public void PopulatesCustomTableWithLocalization() | ||
| 49 | { | ||
| 50 | var folder = TestData.Get(@"TestData"); | ||
| 51 | |||
| 52 | using (var fs = new DisposableFileSystem()) | ||
| 53 | { | ||
| 54 | var baseFolder = fs.GetFolder(); | ||
| 55 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 56 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 57 | |||
| 58 | var result = WixRunner.Execute(new[] | ||
| 59 | { | ||
| 60 | "build", | ||
| 61 | Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"), | ||
| 62 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 63 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 64 | "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"), | ||
| 65 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 66 | "-intermediateFolder", intermediateFolder, | ||
| 67 | "-o", msiPath | ||
| 68 | }); | ||
| 69 | |||
| 70 | result.AssertSuccess(); | ||
| 71 | |||
| 72 | Assert.True(File.Exists(msiPath)); | ||
| 73 | var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" }); | ||
| 74 | Assert.Equal(new[] | ||
| 75 | { | ||
| 76 | "CustomTableLocalized:Row1\tThis is row one", | ||
| 77 | "CustomTableLocalized:Row2\tThis is row two", | ||
| 78 | }, results); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | [Fact] | ||
| 83 | public void PopulatesCustomTableWithFilePath() | ||
| 84 | { | ||
| 85 | var folder = TestData.Get(@"TestData"); | ||
| 86 | |||
| 87 | using (var fs = new DisposableFileSystem()) | ||
| 88 | { | ||
| 89 | var baseFolder = fs.GetFolder(); | ||
| 90 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 91 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 92 | |||
| 93 | var result = WixRunner.Execute(new[] | ||
| 94 | { | ||
| 95 | "build", | ||
| 96 | Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), | ||
| 97 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 98 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 99 | "-bindpath", Path.Combine(folder, "CustomTable", "data"), | ||
| 100 | "-intermediateFolder", intermediateFolder, | ||
| 101 | "-o", msiPath | ||
| 102 | }); | ||
| 103 | |||
| 104 | result.AssertSuccess(); | ||
| 105 | |||
| 106 | Assert.True(File.Exists(msiPath)); | ||
| 107 | var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); | ||
| 108 | Assert.Equal(new[] | ||
| 109 | { | ||
| 110 | "CustomTableWithFile:Row1\t[Binary data]", | ||
| 111 | "CustomTableWithFile:Row2\t[Binary data]", | ||
| 112 | }, results); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | [Fact] | ||
| 117 | public void PopulatesCustomTableWithFilePathSerialized() | ||
| 118 | { | ||
| 119 | var folder = TestData.Get(@"TestData"); | ||
| 120 | |||
| 121 | using (var fs = new DisposableFileSystem()) | ||
| 122 | { | ||
| 123 | var baseFolder = fs.GetFolder(); | ||
| 124 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 125 | var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib"); | ||
| 126 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 127 | |||
| 128 | var result = WixRunner.Execute(new[] | ||
| 129 | { | ||
| 130 | "build", | ||
| 131 | Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), | ||
| 132 | "-bindpath", Path.Combine(folder, "CustomTable", "data"), | ||
| 133 | "-intermediateFolder", intermediateFolder, | ||
| 134 | "-o", wixlibPath | ||
| 135 | }); | ||
| 136 | |||
| 137 | result.AssertSuccess(); | ||
| 138 | |||
| 139 | result = WixRunner.Execute(new[] | ||
| 140 | { | ||
| 141 | "build", | ||
| 142 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 143 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 144 | "-lib", wixlibPath, | ||
| 145 | "-bindpath", Path.Combine(folder, "CustomTable", "data"), | ||
| 146 | "-intermediateFolder", intermediateFolder, | ||
| 147 | "-o", msiPath | ||
| 148 | }); | ||
| 149 | |||
| 150 | result.AssertSuccess(); | ||
| 151 | |||
| 152 | Assert.True(File.Exists(msiPath)); | ||
| 153 | var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); | ||
| 154 | Assert.Equal(new[] | ||
| 155 | { | ||
| 156 | "CustomTableWithFile:Row1\t[Binary data]", | ||
| 157 | "CustomTableWithFile:Row2\t[Binary data]", | ||
| 158 | }, results); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | [Fact] | ||
| 163 | public void UnrealCustomTableIsNotPresentInMsi() | ||
| 164 | { | ||
| 165 | var folder = TestData.Get(@"TestData"); | ||
| 166 | |||
| 167 | using (var fs = new DisposableFileSystem()) | ||
| 168 | { | ||
| 169 | var baseFolder = fs.GetFolder(); | ||
| 170 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 171 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 172 | |||
| 173 | var result = WixRunner.Execute(new[] | ||
| 174 | { | ||
| 175 | "build", | ||
| 176 | Path.Combine(folder, "CustomTable", "CustomTable.wxs"), | ||
| 177 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 178 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 179 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 180 | "-intermediateFolder", intermediateFolder, | ||
| 181 | "-o", msiPath | ||
| 182 | }); | ||
| 183 | |||
| 184 | result.AssertSuccess(); | ||
| 185 | |||
| 186 | Assert.True(File.Exists(msiPath)); | ||
| 187 | var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); | ||
| 188 | Assert.Empty(results); | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | [Fact] | ||
| 193 | public void CanCompileAndDecompile() | ||
| 194 | { | ||
| 195 | var folder = TestData.Get(@"TestData"); | ||
| 196 | var expectedFile = Path.Combine(folder, "CustomTable", "CustomTable-Expected.wxs"); | ||
| 197 | |||
| 198 | using (var fs = new DisposableFileSystem()) | ||
| 199 | { | ||
| 200 | var baseFolder = fs.GetFolder(); | ||
| 201 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 202 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 203 | var decompiledWxsPath = Path.Combine(baseFolder, @"decompiled.wxs"); | ||
| 204 | |||
| 205 | var result = WixRunner.Execute(new[] | ||
| 206 | { | ||
| 207 | "build", | ||
| 208 | "-d", "ProductCode=83f9c623-26fe-42ab-951e-170022117f54", | ||
| 209 | Path.Combine(folder, "CustomTable", "CustomTable.wxs"), | ||
| 210 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 211 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 212 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 213 | "-intermediateFolder", intermediateFolder, | ||
| 214 | "-o", msiPath | ||
| 215 | }); | ||
| 216 | |||
| 217 | result.AssertSuccess(); | ||
| 218 | Assert.True(File.Exists(msiPath)); | ||
| 219 | |||
| 220 | result = WixRunner.Execute(new[] | ||
| 221 | { | ||
| 222 | "decompile", msiPath, | ||
| 223 | "-sw1060", | ||
| 224 | "-intermediateFolder", intermediateFolder, | ||
| 225 | "-o", decompiledWxsPath | ||
| 226 | }); | ||
| 227 | |||
| 228 | result.AssertSuccess(); | ||
| 229 | |||
| 230 | WixAssert.CompareXml(expectedFile, decompiledWxsPath); | ||
| 231 | } | ||
| 232 | } | ||
| 233 | } | ||
| 234 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs new file mode 100644 index 00000000..ab04da15 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixBuildTools.TestSupport; | ||
| 7 | using WixToolset.Core.TestPackage; | ||
| 8 | using Xunit; | ||
| 9 | |||
| 10 | public class DecompileFixture | ||
| 11 | { | ||
| 12 | private static void DecompileAndCompare(string sourceFolder, string msiName, string expectedWxsName) | ||
| 13 | { | ||
| 14 | var folder = TestData.Get(sourceFolder); | ||
| 15 | |||
| 16 | using (var fs = new DisposableFileSystem()) | ||
| 17 | { | ||
| 18 | var intermediateFolder = fs.GetFolder(); | ||
| 19 | var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); | ||
| 20 | |||
| 21 | var result = WixRunner.Execute(new[] | ||
| 22 | { | ||
| 23 | "decompile", | ||
| 24 | Path.Combine(folder, msiName), | ||
| 25 | "-intermediateFolder", intermediateFolder, | ||
| 26 | "-o", outputPath | ||
| 27 | }); | ||
| 28 | |||
| 29 | result.AssertSuccess(); | ||
| 30 | |||
| 31 | WixAssert.CompareXml(Path.Combine(folder, expectedWxsName), outputPath); | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | [Fact] | ||
| 36 | public void CanDecompileSingleFileCompressed() | ||
| 37 | { | ||
| 38 | DecompileAndCompare(@"TestData\DecompileSingleFileCompressed", "example.msi", "Expected.wxs"); | ||
| 39 | } | ||
| 40 | |||
| 41 | [Fact] | ||
| 42 | public void CanDecompile64BitSingleFileCompressed() | ||
| 43 | { | ||
| 44 | DecompileAndCompare(@"TestData\DecompileSingleFileCompressed64", "example.msi", "Expected.wxs"); | ||
| 45 | } | ||
| 46 | |||
| 47 | [Fact] | ||
| 48 | public void CanDecompileNestedDirSearchUnderRegSearch() | ||
| 49 | { | ||
| 50 | DecompileAndCompare(@"TestData\AppSearch", "NestedDirSearchUnderRegSearch.msi", "DecompiledNestedDirSearchUnderRegSearch.wxs"); | ||
| 51 | } | ||
| 52 | |||
| 53 | [Fact] | ||
| 54 | public void CanDecompileOldClassTableDefinition() | ||
| 55 | { | ||
| 56 | // The input MSI was not created using standard methods, it is an example of a real world database that needs to be decompiled. | ||
| 57 | // The Class/@Feature_ column has length of 32, the File/@Attributes has length of 2, | ||
| 58 | // and numerous foreign key relationships are missing. | ||
| 59 | DecompileAndCompare(@"TestData\Class", "OldClassTableDef.msi", "DecompiledOldClassTableDef.wxs"); | ||
| 60 | } | ||
| 61 | |||
| 62 | [Fact] | ||
| 63 | public void CanDecompileSequenceTables() | ||
| 64 | { | ||
| 65 | DecompileAndCompare(@"TestData\SequenceTables", "SequenceTables.msi", "DecompiledSequenceTables.wxs"); | ||
| 66 | } | ||
| 67 | |||
| 68 | [Fact] | ||
| 69 | public void CanDecompileShortcuts() | ||
| 70 | { | ||
| 71 | DecompileAndCompare(@"TestData\Shortcut", "shortcuts.msi", "DecompiledShortcuts.wxs"); | ||
| 72 | } | ||
| 73 | |||
| 74 | [Fact] | ||
| 75 | public void CanDecompileNullComponent() | ||
| 76 | { | ||
| 77 | DecompileAndCompare(@"TestData\DecompileNullComponent", "example.msi", "Expected.wxs"); | ||
| 78 | } | ||
| 79 | |||
| 80 | [Fact] | ||
| 81 | public void CanDecompileMergeModuleWithTargetDirComponent() | ||
| 82 | { | ||
| 83 | DecompileAndCompare(@"TestData\DecompileTargetDirMergeModule", "MergeModule1.msm", "Expected.wxs"); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs new file mode 100644 index 00000000..840b411e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs | |||
| @@ -0,0 +1,180 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using System.Xml; | ||
| 9 | using WixBuildTools.TestSupport; | ||
| 10 | using WixToolset.Core.TestPackage; | ||
| 11 | using Xunit; | ||
| 12 | |||
| 13 | public class DependencyExtensionFixture | ||
| 14 | { | ||
| 15 | [Fact] | ||
| 16 | public void CanBuildBundleUsingExePackageWithProvides() | ||
| 17 | { | ||
| 18 | var folder = TestData.Get(@"TestData"); | ||
| 19 | |||
| 20 | using (var fs = new DisposableFileSystem()) | ||
| 21 | { | ||
| 22 | var baseFolder = fs.GetFolder(); | ||
| 23 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 24 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 25 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 26 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 27 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 28 | |||
| 29 | var result = WixRunner.Execute(new[] | ||
| 30 | { | ||
| 31 | "build", | ||
| 32 | Path.Combine(folder, "Dependency", "ExePackageProvidesBundle.wxs"), | ||
| 33 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 34 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 35 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 36 | "-intermediateFolder", intermediateFolder, | ||
| 37 | "-o", bundlePath, | ||
| 38 | }); | ||
| 39 | |||
| 40 | result.AssertSuccess(); | ||
| 41 | |||
| 42 | Assert.True(File.Exists(bundlePath)); | ||
| 43 | |||
| 44 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 45 | extractResult.AssertSuccess(); | ||
| 46 | |||
| 47 | var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage/burn:Provides") | ||
| 48 | .Cast<XmlElement>() | ||
| 49 | .Select(e => e.GetTestXml()) | ||
| 50 | .ToArray(); | ||
| 51 | WixAssert.CompareLineByLine(new string[] | ||
| 52 | { | ||
| 53 | "<Provides Key='DependencyTests_ExeA,v1.0' Version='1.0.0.0' DisplayName='Windows Installer XML Toolset' />", | ||
| 54 | }, provides); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | [Fact] | ||
| 59 | public void CanBuildBundleUsingMsiWithProvides() | ||
| 60 | { | ||
| 61 | var folder = TestData.Get(@"TestData"); | ||
| 62 | |||
| 63 | using (var fs = new DisposableFileSystem()) | ||
| 64 | { | ||
| 65 | var baseFolder = fs.GetFolder(); | ||
| 66 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 67 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 68 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 69 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 70 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 71 | |||
| 72 | var result = WixRunner.Execute(new[] | ||
| 73 | { | ||
| 74 | "build", | ||
| 75 | Path.Combine(folder, "UsingProvides", "Package.wxs"), | ||
| 76 | Path.Combine(folder, "UsingProvides", "PackageComponents.wxs"), | ||
| 77 | "-loc", Path.Combine(folder, "UsingProvides", "Package.en-us.wxl"), | ||
| 78 | "-bindpath", Path.Combine(folder, "UsingProvides"), | ||
| 79 | "-intermediateFolder", intermediateFolder, | ||
| 80 | "-o", Path.Combine(binFolder, "UsingProvides.msi"), | ||
| 81 | }); | ||
| 82 | |||
| 83 | result.AssertSuccess(); | ||
| 84 | |||
| 85 | result = WixRunner.Execute(new[] | ||
| 86 | { | ||
| 87 | "build", | ||
| 88 | Path.Combine(folder, "Dependency", "UsingProvidesBundle.wxs"), | ||
| 89 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 90 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 91 | "-bindpath", binFolder, | ||
| 92 | "-intermediateFolder", intermediateFolder, | ||
| 93 | "-o", bundlePath, | ||
| 94 | }); | ||
| 95 | |||
| 96 | result.AssertSuccess(); | ||
| 97 | |||
| 98 | Assert.True(File.Exists(bundlePath)); | ||
| 99 | |||
| 100 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 101 | extractResult.AssertSuccess(); | ||
| 102 | |||
| 103 | var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage/burn:Provides") | ||
| 104 | .Cast<XmlElement>() | ||
| 105 | .Select(e => e.GetTestXml()) | ||
| 106 | .ToArray(); | ||
| 107 | WixAssert.CompareLineByLine(new string[] | ||
| 108 | { | ||
| 109 | "<Provides Key='UsingProvides' Version='1.0.0.0' DisplayName='MsiPackage' Imported='yes' />", | ||
| 110 | "<Provides Key='{A81D50F9-B696-4F3D-ABE0-E64D61590E5F}' Version='1.0.0.0' DisplayName='MsiPackage' />", | ||
| 111 | }, provides); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | [Fact] | ||
| 116 | public void CanBuildBundleWithCustomProviderKey() | ||
| 117 | { | ||
| 118 | var folder = TestData.Get(@"TestData"); | ||
| 119 | |||
| 120 | using (var fs = new DisposableFileSystem()) | ||
| 121 | { | ||
| 122 | var baseFolder = fs.GetFolder(); | ||
| 123 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 124 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 125 | var bundlePath = Path.Combine(binFolder, "test.exe"); | ||
| 126 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 127 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 128 | |||
| 129 | var result = WixRunner.Execute(new[] | ||
| 130 | { | ||
| 131 | "build", | ||
| 132 | Path.Combine(folder, "Dependency", "CustomProviderKeyBundle.wxs"), | ||
| 133 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 134 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 135 | "-intermediateFolder", intermediateFolder, | ||
| 136 | "-o", bundlePath, | ||
| 137 | }); | ||
| 138 | |||
| 139 | result.AssertSuccess(); | ||
| 140 | |||
| 141 | Assert.True(File.Exists(bundlePath)); | ||
| 142 | |||
| 143 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 144 | extractResult.AssertSuccess(); | ||
| 145 | |||
| 146 | var ignoreAttributesByElementName = new Dictionary<string, List<string>> | ||
| 147 | { | ||
| 148 | { "Registration", new List<string> { "Id" } }, | ||
| 149 | }; | ||
| 150 | var registration = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration") | ||
| 151 | .Cast<XmlElement>() | ||
| 152 | .Select(e => e.GetTestXml(ignoreAttributesByElementName)) | ||
| 153 | .ToArray(); | ||
| 154 | WixAssert.CompareLineByLine(new string[] | ||
| 155 | { | ||
| 156 | "<Registration Id='*' ExecutableName='test.exe' PerMachine='yes' Tag='' Version='1.0.0.0' ProviderKey='MyProviderKey,v1.0'><Arp Register='yes' DisplayName='BurnBundle' DisplayVersion='1.0.0.0' Publisher='Example Corporation' /></Registration>", | ||
| 157 | }, registration); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | [Fact] | ||
| 162 | public void CanBuildPackageUsingProvides() | ||
| 163 | { | ||
| 164 | var folder = TestData.Get(@"TestData\UsingProvides"); | ||
| 165 | var build = new Builder(folder, null, new[] { folder }); | ||
| 166 | |||
| 167 | var results = build.BuildAndQuery(Build, "WixDependencyProvider"); | ||
| 168 | Assert.Equal(new[] | ||
| 169 | { | ||
| 170 | "WixDependencyProvider:dep74OfIcniaqxA7EprRGBw4Oyy3r8\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tUsingProvides\t\t\t", | ||
| 171 | }, results); | ||
| 172 | } | ||
| 173 | |||
| 174 | private static void Build(string[] args) | ||
| 175 | { | ||
| 176 | var result = WixRunner.Execute(args) | ||
| 177 | .AssertSuccess(); | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs new file mode 100644 index 00000000..a61bdff3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs | |||
| @@ -0,0 +1,271 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using WixBuildTools.TestSupport; | ||
| 9 | using WixToolset.Core.TestPackage; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.WindowsInstaller; | ||
| 12 | using Xunit; | ||
| 13 | |||
| 14 | public class DirectoryFixture | ||
| 15 | { | ||
| 16 | [Fact] | ||
| 17 | public void CanGet32bitProgramFiles6432Folder() | ||
| 18 | { | ||
| 19 | var folder = TestData.Get(@"TestData"); | ||
| 20 | |||
| 21 | using (var fs = new DisposableFileSystem()) | ||
| 22 | { | ||
| 23 | var baseFolder = fs.GetFolder(); | ||
| 24 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 25 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 26 | |||
| 27 | var result = WixRunner.Execute(new[] | ||
| 28 | { | ||
| 29 | "build", | ||
| 30 | Path.Combine(folder, "Directory", "Empty.wxs"), | ||
| 31 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 32 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 33 | "-intermediateFolder", intermediateFolder, | ||
| 34 | "-o", msiPath | ||
| 35 | }); | ||
| 36 | |||
| 37 | result.AssertSuccess(); | ||
| 38 | |||
| 39 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 40 | var section = intermediate.Sections.Single(); | ||
| 41 | |||
| 42 | var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList(); | ||
| 43 | Assert.Equal(new[] | ||
| 44 | { | ||
| 45 | "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", | ||
| 46 | "ProgramFiles6432Folder:ProgramFilesFolder:.", | ||
| 47 | "ProgramFilesFolder:TARGETDIR:PFiles", | ||
| 48 | "TARGETDIR::SourceDir" | ||
| 49 | }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | [Fact] | ||
| 54 | public void CanGet64bitProgramFiles6432Folder() | ||
| 55 | { | ||
| 56 | var folder = TestData.Get(@"TestData"); | ||
| 57 | |||
| 58 | using (var fs = new DisposableFileSystem()) | ||
| 59 | { | ||
| 60 | var baseFolder = fs.GetFolder(); | ||
| 61 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 62 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 63 | |||
| 64 | var result = WixRunner.Execute(new[] | ||
| 65 | { | ||
| 66 | "build", | ||
| 67 | "-arch", "x64", | ||
| 68 | Path.Combine(folder, "Directory", "Empty.wxs"), | ||
| 69 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 70 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 71 | "-intermediateFolder", intermediateFolder, | ||
| 72 | "-o", msiPath | ||
| 73 | }); | ||
| 74 | |||
| 75 | result.AssertSuccess(); | ||
| 76 | |||
| 77 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 78 | var section = intermediate.Sections.Single(); | ||
| 79 | |||
| 80 | var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList(); | ||
| 81 | Assert.Equal(new[] | ||
| 82 | { | ||
| 83 | "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", | ||
| 84 | "ProgramFiles6432Folder:ProgramFiles64Folder:.", | ||
| 85 | "ProgramFiles64Folder:TARGETDIR:PFiles64", | ||
| 86 | "TARGETDIR::SourceDir" | ||
| 87 | }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | [Fact] | ||
| 92 | public void CanGetDefaultName() | ||
| 93 | { | ||
| 94 | var folder = TestData.Get(@"TestData"); | ||
| 95 | |||
| 96 | using (var fs = new DisposableFileSystem()) | ||
| 97 | { | ||
| 98 | var baseFolder = fs.GetFolder(); | ||
| 99 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 100 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 101 | |||
| 102 | var result = WixRunner.Execute(new[] | ||
| 103 | { | ||
| 104 | "build", | ||
| 105 | Path.Combine(folder, "Directory", "DefaultName.wxs"), | ||
| 106 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 107 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 108 | "-intermediateFolder", intermediateFolder, | ||
| 109 | "-o", msiPath | ||
| 110 | }); | ||
| 111 | |||
| 112 | result.AssertSuccess(); | ||
| 113 | |||
| 114 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 115 | var section = intermediate.Sections.Single(); | ||
| 116 | |||
| 117 | var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList(); | ||
| 118 | WixAssert.CompareLineByLine(new[] | ||
| 119 | { | ||
| 120 | "BinFolder\tCompanyFolder\t.", | ||
| 121 | "CompanyFolder\tProgramFilesFolder\tExample Corporation", | ||
| 122 | "ProgramFilesFolder\tTARGETDIR\tPFiles", | ||
| 123 | "TARGETDIR\t\tSourceDir" | ||
| 124 | }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); | ||
| 125 | |||
| 126 | var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 127 | var directoryRows = data.Tables["Directory"].Rows; | ||
| 128 | WixAssert.CompareLineByLine(new[] | ||
| 129 | { | ||
| 130 | "BinFolder\tCompanyFolder\t.", | ||
| 131 | "CompanyFolder\tProgramFilesFolder\tu7-b4gch|Example Corporation", | ||
| 132 | "ProgramFilesFolder\tTARGETDIR\tPFiles", | ||
| 133 | "TARGETDIR\t\tSourceDir" | ||
| 134 | }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | [Fact] | ||
| 139 | public void CanGetDuplicateDir() | ||
| 140 | { | ||
| 141 | var folder = TestData.Get(@"TestData"); | ||
| 142 | |||
| 143 | using (var fs = new DisposableFileSystem()) | ||
| 144 | { | ||
| 145 | var baseFolder = fs.GetFolder(); | ||
| 146 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 147 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 148 | |||
| 149 | var result = WixRunner.Execute(new[] | ||
| 150 | { | ||
| 151 | "build", | ||
| 152 | "-arch", "x64", | ||
| 153 | Path.Combine(folder, "DuplicateDir", "DuplicateDir.wxs"), | ||
| 154 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 155 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 156 | "-intermediateFolder", intermediateFolder, | ||
| 157 | "-o", msiPath | ||
| 158 | }); | ||
| 159 | |||
| 160 | result.AssertSuccess(); | ||
| 161 | |||
| 162 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 163 | var section = intermediate.Sections.Single(); | ||
| 164 | |||
| 165 | var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList(); | ||
| 166 | Assert.Equal(new[] | ||
| 167 | { | ||
| 168 | "dZsSsu81KcG46xXTwc4mTSZO5Zx4:INSTALLFOLDER:dupe", | ||
| 169 | "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", | ||
| 170 | "ProgramFiles6432Folder:ProgramFiles64Folder:.", | ||
| 171 | "ProgramFiles64Folder:TARGETDIR:PFiles64", | ||
| 172 | "TARGETDIR::SourceDir" | ||
| 173 | }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | [Fact] | ||
| 178 | public void CanGetWithMultiNestedSubdirectory() | ||
| 179 | { | ||
| 180 | var folder = TestData.Get(@"TestData"); | ||
| 181 | |||
| 182 | using (var fs = new DisposableFileSystem()) | ||
| 183 | { | ||
| 184 | var baseFolder = fs.GetFolder(); | ||
| 185 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 186 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 187 | |||
| 188 | var result = WixRunner.Execute(new[] | ||
| 189 | { | ||
| 190 | "build", | ||
| 191 | "-arch", "x64", | ||
| 192 | Path.Combine(folder, "Directory", "Nested.wxs"), | ||
| 193 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 194 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 195 | "-intermediateFolder", intermediateFolder, | ||
| 196 | "-o", msiPath | ||
| 197 | }); | ||
| 198 | |||
| 199 | result.AssertSuccess(); | ||
| 200 | |||
| 201 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 202 | var section = intermediate.Sections.Single(); | ||
| 203 | |||
| 204 | var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList(); | ||
| 205 | Assert.Equal(new[] | ||
| 206 | { | ||
| 207 | "BinFolder:ProgramFilesFolder:Example Corporation\\Test Product\\bin", | ||
| 208 | "ProgramFilesFolder:TARGETDIR:PFiles", | ||
| 209 | "TARGETDIR::SourceDir" | ||
| 210 | }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); | ||
| 211 | |||
| 212 | var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 213 | var directoryRows = data.Tables["Directory"].Rows; | ||
| 214 | Assert.Equal(new[] | ||
| 215 | { | ||
| 216 | "d4EceYatXTyy8HXPt5B6DT9Rj.wE:ProgramFilesFolder:u7-b4gch|Example Corporation", | ||
| 217 | "dSJ1pgiASlW7kJTu0wqsGBklJsS0:d4EceYatXTyy8HXPt5B6DT9Rj.wE:vjj-gxay|Test Product", | ||
| 218 | "BinFolder:dSJ1pgiASlW7kJTu0wqsGBklJsS0:bin", | ||
| 219 | "ProgramFilesFolder:TARGETDIR:PFiles", | ||
| 220 | "TARGETDIR::SourceDir" | ||
| 221 | }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray()); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | [Fact] | ||
| 226 | public void CanGetDuplicateTargetSourceName() | ||
| 227 | { | ||
| 228 | var folder = TestData.Get(@"TestData"); | ||
| 229 | |||
| 230 | using (var fs = new DisposableFileSystem()) | ||
| 231 | { | ||
| 232 | var baseFolder = fs.GetFolder(); | ||
| 233 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 234 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 235 | |||
| 236 | var result = WixRunner.Execute(new[] | ||
| 237 | { | ||
| 238 | "build", | ||
| 239 | "-arch", "x64", | ||
| 240 | Path.Combine(folder, "Directory", "DuplicateTargetSourceName.wxs"), | ||
| 241 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 242 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 243 | "-intermediateFolder", intermediateFolder, | ||
| 244 | "-o", msiPath | ||
| 245 | }); | ||
| 246 | |||
| 247 | result.AssertSuccess(); | ||
| 248 | |||
| 249 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 250 | var section = intermediate.Sections.Single(); | ||
| 251 | |||
| 252 | var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList(); | ||
| 253 | Assert.Equal(new[] | ||
| 254 | { | ||
| 255 | "BinFolder\tProgramFilesFolder\tbin", | ||
| 256 | "ProgramFilesFolder\tTARGETDIR\tPFiles", | ||
| 257 | "TARGETDIR\t\tSourceDir" | ||
| 258 | }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); | ||
| 259 | |||
| 260 | var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 261 | var directoryRows = data.Tables["Directory"].Rows; | ||
| 262 | Assert.Equal(new[] | ||
| 263 | { | ||
| 264 | "BinFolder\tProgramFilesFolder\tbin", | ||
| 265 | "ProgramFilesFolder\tTARGETDIR\tPFiles", | ||
| 266 | "TARGETDIR\t\tSourceDir" | ||
| 267 | }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); | ||
| 268 | } | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs new file mode 100644 index 00000000..e2306dcd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixBuildTools.TestSupport; | ||
| 7 | using WixToolset.Core.TestPackage; | ||
| 8 | using Xunit; | ||
| 9 | |||
| 10 | public class ExePackageFixture | ||
| 11 | { | ||
| 12 | [Fact] | ||
| 13 | public void ErrorWhenMissingDetectCondition() | ||
| 14 | { | ||
| 15 | var folder = TestData.Get(@"TestData", "ExePackage"); | ||
| 16 | |||
| 17 | using (var fs = new DisposableFileSystem()) | ||
| 18 | { | ||
| 19 | var baseFolder = fs.GetFolder(); | ||
| 20 | |||
| 21 | var result = WixRunner.Execute(new[] | ||
| 22 | { | ||
| 23 | "build", | ||
| 24 | Path.Combine(folder, "MissingDetectCondition.wxs"), | ||
| 25 | "-o", Path.Combine(baseFolder, "test.wixlib") | ||
| 26 | }); | ||
| 27 | |||
| 28 | Assert.Equal(1153, result.ExitCode); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | [Fact] | ||
| 33 | public void ErrorWhenRequireDetectCondition() | ||
| 34 | { | ||
| 35 | var folder = TestData.Get(@"TestData", "ExePackage"); | ||
| 36 | |||
| 37 | using (var fs = new DisposableFileSystem()) | ||
| 38 | { | ||
| 39 | var baseFolder = fs.GetFolder(); | ||
| 40 | |||
| 41 | var result = WixRunner.Execute(new[] | ||
| 42 | { | ||
| 43 | "build", | ||
| 44 | Path.Combine(folder, "RequireDetectCondition.wxs"), | ||
| 45 | "-o", Path.Combine(baseFolder, "test.wixlib") | ||
| 46 | }); | ||
| 47 | |||
| 48 | Assert.Equal(401, result.ExitCode); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs new file mode 100644 index 00000000..089658e6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs | |||
| @@ -0,0 +1,153 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using Example.Extension; | ||
| 9 | using WixBuildTools.TestSupport; | ||
| 10 | using WixToolset.Core.TestPackage; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using Xunit; | ||
| 14 | |||
| 15 | public class ExtensionFixture | ||
| 16 | { | ||
| 17 | [Fact] | ||
| 18 | public void CanBuildAndQuery() | ||
| 19 | { | ||
| 20 | var folder = TestData.Get(@"TestData\ExampleExtension"); | ||
| 21 | var build = new Builder(folder, typeof(ExampleExtensionFactory), new[] { Path.Combine(folder, "data") }); | ||
| 22 | |||
| 23 | var results = build.BuildAndQuery(Build, "Wix4Example"); | ||
| 24 | Assert.Equal(new[] | ||
| 25 | { | ||
| 26 | "Wix4Example:Foo\tBar" | ||
| 27 | }, results); | ||
| 28 | } | ||
| 29 | |||
| 30 | [Fact] | ||
| 31 | public void CanBuildWithExampleExtension() | ||
| 32 | { | ||
| 33 | var folder = TestData.Get(@"TestData\ExampleExtension"); | ||
| 34 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 35 | |||
| 36 | using (var fs = new DisposableFileSystem()) | ||
| 37 | { | ||
| 38 | var intermediateFolder = fs.GetFolder(); | ||
| 39 | |||
| 40 | var result = WixRunner.Execute(new[] | ||
| 41 | { | ||
| 42 | "build", | ||
| 43 | Path.Combine(folder, "Package.wxs"), | ||
| 44 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 45 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 46 | "-ext", extensionPath, | ||
| 47 | "-bindpath", Path.Combine(folder, "data"), | ||
| 48 | "-intermediateFolder", intermediateFolder, | ||
| 49 | "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") | ||
| 50 | }); | ||
| 51 | |||
| 52 | result.AssertSuccess(); | ||
| 53 | |||
| 54 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi"))); | ||
| 55 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); | ||
| 56 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\example.txt"))); | ||
| 57 | |||
| 58 | var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); | ||
| 59 | var section = intermediate.Sections.Single(); | ||
| 60 | |||
| 61 | var fileSymbol = section.Symbols.OfType<FileSymbol>().Single(); | ||
| 62 | Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); | ||
| 63 | Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 64 | |||
| 65 | var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); | ||
| 66 | Assert.Equal("Foo", example.Id?.Id); | ||
| 67 | Assert.Equal("Bar", example[0].AsString()); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | [Fact] | ||
| 72 | public void CanParseCommandLineWithExtension() | ||
| 73 | { | ||
| 74 | var folder = TestData.Get(@"TestData\ExampleExtension"); | ||
| 75 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 76 | |||
| 77 | using (var fs = new DisposableFileSystem()) | ||
| 78 | { | ||
| 79 | var intermediateFolder = fs.GetFolder(); | ||
| 80 | |||
| 81 | var result = WixRunner.Execute(new[] | ||
| 82 | { | ||
| 83 | "build", | ||
| 84 | Path.Combine(folder, "Package.wxs"), | ||
| 85 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 86 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 87 | "-ext", extensionPath, | ||
| 88 | "-bindpath", Path.Combine(folder, "data"), | ||
| 89 | "-intermediateFolder", intermediateFolder, | ||
| 90 | "-example", "test", | ||
| 91 | "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") | ||
| 92 | }); | ||
| 93 | |||
| 94 | result.AssertSuccess(); | ||
| 95 | |||
| 96 | var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); | ||
| 97 | var section = intermediate.Sections.Single(); | ||
| 98 | |||
| 99 | var property = section.Symbols.OfType<PropertySymbol>().Where(p => p.Id.Id == "ExampleProperty").Single(); | ||
| 100 | Assert.Equal("ExampleProperty", property.Id.Id); | ||
| 101 | Assert.Equal("test", property.Value); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | [Fact] | ||
| 106 | public void CannotBuildWithMissingExtension() | ||
| 107 | { | ||
| 108 | var folder = TestData.Get(@"TestData\ExampleExtension"); | ||
| 109 | |||
| 110 | using (var fs = new DisposableFileSystem()) | ||
| 111 | { | ||
| 112 | var intermediateFolder = fs.GetFolder(); | ||
| 113 | |||
| 114 | var exception = Assert.Throws<WixException>(() => | ||
| 115 | WixRunner.Execute(new[] | ||
| 116 | { | ||
| 117 | "build", | ||
| 118 | Path.Combine(folder, "Package.wxs"), | ||
| 119 | "-ext", "ExampleExtension.DoesNotExist" | ||
| 120 | })); | ||
| 121 | |||
| 122 | Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist' could not be found. Checked paths: ", exception.Message); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | [Fact] | ||
| 127 | public void CannotBuildWithMissingVersionedExtension() | ||
| 128 | { | ||
| 129 | var folder = TestData.Get(@"TestData\ExampleExtension"); | ||
| 130 | |||
| 131 | using (var fs = new DisposableFileSystem()) | ||
| 132 | { | ||
| 133 | var intermediateFolder = fs.GetFolder(); | ||
| 134 | |||
| 135 | var exception = Assert.Throws<WixException>(() => | ||
| 136 | WixRunner.Execute(new[] | ||
| 137 | { | ||
| 138 | "build", | ||
| 139 | Path.Combine(folder, "Package.wxs"), | ||
| 140 | "-ext", "ExampleExtension.DoesNotExist/1.0.0" | ||
| 141 | })); | ||
| 142 | |||
| 143 | Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist/1.0.0' could not be found. Checked paths: ", exception.Message); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | private static void Build(string[] args) | ||
| 148 | { | ||
| 149 | var result = WixRunner.Execute(args) | ||
| 150 | .AssertSuccess(); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs new file mode 100644 index 00000000..db9708a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs | |||
| @@ -0,0 +1,174 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Linq; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core.TestPackage; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using WixToolset.Data.WindowsInstaller; | ||
| 12 | using Xunit; | ||
| 13 | |||
| 14 | public class LanguageFixture | ||
| 15 | { | ||
| 16 | [Fact] | ||
| 17 | public void CanBuildWithDefaultProductLanguage() | ||
| 18 | { | ||
| 19 | var folder = TestData.Get(@"TestData", "Language"); | ||
| 20 | |||
| 21 | using (var fs = new DisposableFileSystem()) | ||
| 22 | { | ||
| 23 | var baseFolder = fs.GetFolder(); | ||
| 24 | |||
| 25 | var result = WixRunner.Execute(new[] | ||
| 26 | { | ||
| 27 | "build", | ||
| 28 | Path.Combine(folder, "Package.wxs"), | ||
| 29 | "-loc", Path.Combine(folder, "Package.wxl"), | ||
| 30 | "-bindpath", Path.Combine(folder, "data"), | ||
| 31 | "-intermediateFolder", Path.Combine(baseFolder, "obj"), | ||
| 32 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 33 | }); | ||
| 34 | |||
| 35 | result.AssertSuccess(); | ||
| 36 | |||
| 37 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 38 | var section = intermediate.Sections.Single(); | ||
| 39 | |||
| 40 | var directorySymbols = section.Symbols.OfType<DirectorySymbol>(); | ||
| 41 | WixAssert.CompareLineByLine(new[] | ||
| 42 | { | ||
| 43 | "INSTALLFOLDER:Example Corporation\\MsiPackage", | ||
| 44 | "ProgramFilesFolder:PFiles", | ||
| 45 | "TARGETDIR:SourceDir" | ||
| 46 | }, directorySymbols.OrderBy(s => s.Id.Id).Select(s => s.Id.Id + ":" + s.Name).ToArray()); | ||
| 47 | |||
| 48 | var propertySymbol = section.Symbols.OfType<PropertySymbol>().Single(p => p.Id.Id == "ProductLanguage"); | ||
| 49 | Assert.Equal("0", propertySymbol.Value); | ||
| 50 | |||
| 51 | var summaryPlatform = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); | ||
| 52 | Assert.Equal("Intel;0", summaryPlatform.Value); | ||
| 53 | |||
| 54 | var summaryCodepage = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.Codepage); | ||
| 55 | Assert.Equal("1252", summaryCodepage.Value); | ||
| 56 | |||
| 57 | var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 58 | var directoryRows = data.Tables["Directory"].Rows; | ||
| 59 | WixAssert.CompareLineByLine(new[] | ||
| 60 | { | ||
| 61 | "d4EceYatXTyy8HXPt5B6DT9Rj.wE:u7-b4gch|Example Corporation", | ||
| 62 | "INSTALLFOLDER:oekcr5lq|MsiPackage", | ||
| 63 | "ProgramFilesFolder:PFiles", | ||
| 64 | "TARGETDIR:SourceDir" | ||
| 65 | }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(2)).ToArray()); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | [Fact] | ||
| 70 | public void CanBuildEnuWxl() | ||
| 71 | { | ||
| 72 | var folder = TestData.Get(@"TestData", "Language"); | ||
| 73 | |||
| 74 | using (var fs = new DisposableFileSystem()) | ||
| 75 | { | ||
| 76 | var baseFolder = fs.GetFolder(); | ||
| 77 | |||
| 78 | var result = WixRunner.Execute(new[] | ||
| 79 | { | ||
| 80 | "build", | ||
| 81 | Path.Combine(folder, "Package.wxs"), | ||
| 82 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 83 | "-bindpath", Path.Combine(folder, "data"), | ||
| 84 | "-intermediateFolder", Path.Combine(baseFolder, "obj"), | ||
| 85 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 86 | }); | ||
| 87 | |||
| 88 | result.AssertSuccess(); | ||
| 89 | |||
| 90 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 91 | var section = intermediate.Sections.Single(); | ||
| 92 | |||
| 93 | var propertySymbol = section.Symbols.OfType<PropertySymbol>().Single(p => p.Id.Id == "ProductLanguage"); | ||
| 94 | Assert.Equal("1033", propertySymbol.Value); | ||
| 95 | |||
| 96 | var summaryPlatform = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); | ||
| 97 | Assert.Equal("Intel;1033", summaryPlatform.Value); | ||
| 98 | |||
| 99 | var summaryCodepage = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.Codepage); | ||
| 100 | Assert.Equal("1252", summaryCodepage.Value); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | [Fact] | ||
| 105 | public void CanBuildJpnWxl() | ||
| 106 | { | ||
| 107 | var folder = TestData.Get(@"TestData", "Language"); | ||
| 108 | |||
| 109 | using (var fs = new DisposableFileSystem()) | ||
| 110 | { | ||
| 111 | var baseFolder = fs.GetFolder(); | ||
| 112 | |||
| 113 | var result = WixRunner.Execute(new[] | ||
| 114 | { | ||
| 115 | "build", | ||
| 116 | Path.Combine(folder, "Package.wxs"), | ||
| 117 | "-loc", Path.Combine(folder, "Package.ja-jp.wxl"), | ||
| 118 | "-bindpath", Path.Combine(folder, "data"), | ||
| 119 | "-intermediateFolder", Path.Combine(baseFolder, "obj"), | ||
| 120 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 121 | }); | ||
| 122 | |||
| 123 | result.AssertSuccess(); | ||
| 124 | |||
| 125 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 126 | var section = intermediate.Sections.Single(); | ||
| 127 | |||
| 128 | var propertySymbol = section.Symbols.OfType<PropertySymbol>().Single(p => p.Id.Id == "ProductLanguage"); | ||
| 129 | Assert.Equal("1041", propertySymbol.Value); | ||
| 130 | |||
| 131 | var summaryPlatform = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); | ||
| 132 | Assert.Equal("Intel;1041", summaryPlatform.Value); | ||
| 133 | |||
| 134 | var summaryCodepage = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.Codepage); | ||
| 135 | Assert.Equal("932", summaryCodepage.Value); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | [Fact] | ||
| 140 | public void CanBuildJpnWxlWithEnuSummaryInfo() | ||
| 141 | { | ||
| 142 | var folder = TestData.Get(@"TestData", "Language"); | ||
| 143 | |||
| 144 | using (var fs = new DisposableFileSystem()) | ||
| 145 | { | ||
| 146 | var baseFolder = fs.GetFolder(); | ||
| 147 | |||
| 148 | var result = WixRunner.Execute(new[] | ||
| 149 | { | ||
| 150 | "build", | ||
| 151 | Path.Combine(folder, "Package.wxs"), | ||
| 152 | "-loc", Path.Combine(folder, "PackageWithEnSummaryInfo.ja-jp.wxl"), | ||
| 153 | "-bindpath", Path.Combine(folder, "data"), | ||
| 154 | "-intermediateFolder", Path.Combine(baseFolder, "obj"), | ||
| 155 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 156 | }); | ||
| 157 | |||
| 158 | result.AssertSuccess(); | ||
| 159 | |||
| 160 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 161 | var section = intermediate.Sections.Single(); | ||
| 162 | |||
| 163 | var propertySymbol = section.Symbols.OfType<PropertySymbol>().Single(p => p.Id.Id == "ProductLanguage"); | ||
| 164 | Assert.Equal("1041", propertySymbol.Value); | ||
| 165 | |||
| 166 | var summaryPlatform = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); | ||
| 167 | Assert.Equal("Intel;1041", summaryPlatform.Value); | ||
| 168 | |||
| 169 | var summaryCodepage = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.Codepage); | ||
| 170 | Assert.Equal("1252", summaryCodepage.Value); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs new file mode 100644 index 00000000..cfe4d3f1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs | |||
| @@ -0,0 +1,174 @@ | |||
| 1 | |||
| 2 | // 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. | ||
| 3 | |||
| 4 | namespace WixToolsetTest.CoreIntegration | ||
| 5 | { | ||
| 6 | using System; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using WixBuildTools.TestSupport; | ||
| 10 | using WixToolset.Core; | ||
| 11 | using WixToolset.Core.TestPackage; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Symbols; | ||
| 14 | using WixToolset.Extensibility.Data; | ||
| 15 | using WixToolset.Extensibility.Services; | ||
| 16 | using Xunit; | ||
| 17 | |||
| 18 | public class LinkerFixture | ||
| 19 | { | ||
| 20 | [Fact] | ||
| 21 | public void MustCompileBeforeLinking() | ||
| 22 | { | ||
| 23 | var intermediate1 = new Intermediate("TestIntermediate1", new[] { new IntermediateSection("test1", SectionType.Product) }, null); | ||
| 24 | var intermediate2 = new Intermediate("TestIntermediate2", new[] { new IntermediateSection("test2", SectionType.Fragment) }, null); | ||
| 25 | var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); | ||
| 26 | |||
| 27 | var listener = new TestMessageListener(); | ||
| 28 | var messaging = serviceProvider.GetService<IMessaging>(); | ||
| 29 | messaging.SetListener(listener); | ||
| 30 | |||
| 31 | var creator = serviceProvider.GetService<ISymbolDefinitionCreator>(); | ||
| 32 | var context = serviceProvider.GetService<ILinkContext>(); | ||
| 33 | context.Extensions = Array.Empty<WixToolset.Extensibility.ILinkerExtension>(); | ||
| 34 | context.ExtensionData = Array.Empty<WixToolset.Extensibility.IExtensionData>(); | ||
| 35 | context.Intermediates = new[] { intermediate1, intermediate2 }; | ||
| 36 | context.SymbolDefinitionCreator = creator; | ||
| 37 | |||
| 38 | var linker = serviceProvider.GetService<ILinker>(); | ||
| 39 | linker.Link(context); | ||
| 40 | |||
| 41 | Assert.Equal((int)ErrorMessages.Ids.IntermediatesMustBeCompiled, messaging.LastErrorNumber); | ||
| 42 | Assert.Single(listener.Messages); | ||
| 43 | Assert.EndsWith("TestIntermediate1, TestIntermediate2", listener.Messages[0].ToString()); | ||
| 44 | } | ||
| 45 | |||
| 46 | [Fact] | ||
| 47 | public void CanBuildWithOverridableActions() | ||
| 48 | { | ||
| 49 | var folder = TestData.Get(@"TestData\OverridableActions"); | ||
| 50 | |||
| 51 | using (var fs = new DisposableFileSystem()) | ||
| 52 | { | ||
| 53 | var baseFolder = fs.GetFolder(); | ||
| 54 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 55 | |||
| 56 | var result = WixRunner.Execute(new[] | ||
| 57 | { | ||
| 58 | "build", | ||
| 59 | "-sw1008", // this is expected for this test | ||
| 60 | Path.Combine(folder, "Package.wxs"), | ||
| 61 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 62 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 63 | "-bindpath", Path.Combine(folder, "data"), | ||
| 64 | "-intermediateFolder", intermediateFolder, | ||
| 65 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 66 | }); | ||
| 67 | |||
| 68 | result.AssertSuccess(); | ||
| 69 | |||
| 70 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); | ||
| 71 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 72 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); | ||
| 73 | |||
| 74 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 75 | var section = intermediate.Sections.Single(); | ||
| 76 | |||
| 77 | var actions = section.Symbols.OfType<WixActionSymbol>().Where(wat => wat.Action.StartsWith("Set")).ToList(); | ||
| 78 | Assert.Equal(2, actions.Count); | ||
| 79 | //Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileSymbolFields.Source].AsPath().Path); | ||
| 80 | //Assert.Equal(@"test.txt", wixFile[WixFileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | [Fact] | ||
| 85 | public void MissingEntrySectionDetectedProduct() | ||
| 86 | { | ||
| 87 | var folder = TestData.Get(@"TestData\OverridableActions"); | ||
| 88 | |||
| 89 | using (var fs = new DisposableFileSystem()) | ||
| 90 | { | ||
| 91 | var baseFolder = fs.GetFolder(); | ||
| 92 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 93 | |||
| 94 | try | ||
| 95 | { | ||
| 96 | WixRunner.Execute(new[] | ||
| 97 | { | ||
| 98 | "build", | ||
| 99 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 100 | "-intermediateFolder", intermediateFolder, | ||
| 101 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 102 | }); | ||
| 103 | } | ||
| 104 | catch (WixException we) | ||
| 105 | { | ||
| 106 | Assert.Equal("Could not find entry section in provided list of intermediates. Expected section of type 'Product'.", we.Message); | ||
| 107 | return; | ||
| 108 | } | ||
| 109 | |||
| 110 | Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | [Fact] | ||
| 115 | public void MissingEntrySectionDetectedWixipl() | ||
| 116 | { | ||
| 117 | var folder = TestData.Get(@"TestData\OverridableActions"); | ||
| 118 | |||
| 119 | using (var fs = new DisposableFileSystem()) | ||
| 120 | { | ||
| 121 | var baseFolder = fs.GetFolder(); | ||
| 122 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 123 | |||
| 124 | try | ||
| 125 | { | ||
| 126 | WixRunner.Execute(new[] | ||
| 127 | { | ||
| 128 | "build", | ||
| 129 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 130 | "-intermediateFolder", intermediateFolder, | ||
| 131 | "-o", Path.Combine(baseFolder, @"bin\test.wixipl") | ||
| 132 | }); | ||
| 133 | } | ||
| 134 | catch (WixException we) | ||
| 135 | { | ||
| 136 | Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); | ||
| 137 | return; | ||
| 138 | } | ||
| 139 | |||
| 140 | Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | [Fact] | ||
| 145 | public void MissingEntrySectionDetectedUnknown() | ||
| 146 | { | ||
| 147 | var folder = TestData.Get(@"TestData\OverridableActions"); | ||
| 148 | |||
| 149 | using (var fs = new DisposableFileSystem()) | ||
| 150 | { | ||
| 151 | var baseFolder = fs.GetFolder(); | ||
| 152 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 153 | |||
| 154 | try | ||
| 155 | { | ||
| 156 | WixRunner.Execute(new[] | ||
| 157 | { | ||
| 158 | "build", | ||
| 159 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 160 | "-intermediateFolder", intermediateFolder, | ||
| 161 | "-o", Path.Combine(baseFolder, @"bin\test.bob") | ||
| 162 | }); | ||
| 163 | } | ||
| 164 | catch (WixException we) | ||
| 165 | { | ||
| 166 | Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); | ||
| 167 | return; | ||
| 168 | } | ||
| 169 | |||
| 170 | Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs new file mode 100644 index 00000000..de18e30c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Linq; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core.TestPackage; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using Xunit; | ||
| 11 | |||
| 12 | public class MediaFixture | ||
| 13 | { | ||
| 14 | [Fact] | ||
| 15 | public void CanBuildMultiMedia() | ||
| 16 | { | ||
| 17 | var folder = TestData.Get(@"TestData"); | ||
| 18 | |||
| 19 | using (var fs = new DisposableFileSystem()) | ||
| 20 | { | ||
| 21 | var baseFolder = fs.GetFolder(); | ||
| 22 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 23 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 24 | |||
| 25 | var result = WixRunner.Execute(new[] | ||
| 26 | { | ||
| 27 | "build", | ||
| 28 | Path.Combine(folder, "Media", "MultiMedia.wxs"), | ||
| 29 | "-bindpath", Path.Combine(folder, "Media", "data"), | ||
| 30 | "-intermediateFolder", intermediateFolder, | ||
| 31 | "-o", msiPath | ||
| 32 | }); | ||
| 33 | |||
| 34 | result.AssertSuccess(); | ||
| 35 | |||
| 36 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 37 | var section = intermediate.Sections.Single(); | ||
| 38 | |||
| 39 | var mediaSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.MediaSymbol>().OrderBy(m => m.DiskId).ToList(); | ||
| 40 | var fileSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.FileSymbol>().OrderBy(f => f.Sequence).ToList(); | ||
| 41 | Assert.Equal(1, mediaSymbols[0].DiskId); | ||
| 42 | Assert.Equal(2, mediaSymbols[0].LastSequence); | ||
| 43 | Assert.Equal(2, mediaSymbols[1].DiskId); | ||
| 44 | Assert.Equal(4, mediaSymbols[1].LastSequence); | ||
| 45 | Assert.Equal(new[] | ||
| 46 | { | ||
| 47 | "a1.txt", | ||
| 48 | "a2.txt", | ||
| 49 | "b1.txt", | ||
| 50 | "b2.txt", | ||
| 51 | }, fileSymbols.Select(f => f.Name).ToArray()); | ||
| 52 | Assert.Equal(new[] | ||
| 53 | { | ||
| 54 | 1, | ||
| 55 | 2, | ||
| 56 | 3, | ||
| 57 | 4, | ||
| 58 | }, fileSymbols.Select(f => f.Sequence).ToArray()); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs new file mode 100644 index 00000000..17e91692 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs | |||
| @@ -0,0 +1,113 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using WixBuildTools.TestSupport; | ||
| 9 | using WixToolset.Core.TestPackage; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | using WixToolset.Data.WindowsInstaller; | ||
| 13 | using Xunit; | ||
| 14 | |||
| 15 | public class ModuleFixture | ||
| 16 | { | ||
| 17 | [Fact] | ||
| 18 | public void CanBuildSimpleModule() | ||
| 19 | { | ||
| 20 | var folder = TestData.Get(@"TestData\SimpleModule"); | ||
| 21 | |||
| 22 | using (var fs = new DisposableFileSystem()) | ||
| 23 | { | ||
| 24 | var intermediateFolder = fs.GetFolder(); | ||
| 25 | |||
| 26 | var result = WixRunner.Execute(new[] | ||
| 27 | { | ||
| 28 | "build", | ||
| 29 | Path.Combine(folder, "Module.wxs"), | ||
| 30 | "-loc", Path.Combine(folder, "Module.en-us.wxl"), | ||
| 31 | "-bindpath", Path.Combine(folder, "data"), | ||
| 32 | "-intermediateFolder", intermediateFolder, | ||
| 33 | "-o", Path.Combine(intermediateFolder, @"bin\test.msm") | ||
| 34 | }); | ||
| 35 | |||
| 36 | result.AssertSuccess(); | ||
| 37 | |||
| 38 | var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); | ||
| 39 | Assert.True(File.Exists(msmPath)); | ||
| 40 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); | ||
| 41 | |||
| 42 | var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); | ||
| 43 | var section = intermediate.Sections.Single(); | ||
| 44 | |||
| 45 | var dirSymbols = section.Symbols.OfType<DirectorySymbol>().OrderBy(d => d.Id.Id).ToList(); | ||
| 46 | WixAssert.CompareLineByLine(new[] | ||
| 47 | { | ||
| 48 | "MergeRedirectFolder\tTARGETDIR\t.", | ||
| 49 | "NotTheMergeRedirectFolder\tTARGETDIR\t.", | ||
| 50 | "TARGETDIR\t\tSourceDir" | ||
| 51 | }, dirSymbols.Select(d => String.Join("\t", d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); | ||
| 52 | |||
| 53 | var fileSymbols = section.Symbols.OfType<FileSymbol>().OrderBy(d => d.Id.Id).ToList(); | ||
| 54 | WixAssert.CompareLineByLine(new[] | ||
| 55 | { | ||
| 56 | $"File1\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", | ||
| 57 | $"File2\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", | ||
| 58 | }, fileSymbols.Select(fileSymbol => String.Join("\t", fileSymbol.Id.Id, fileSymbol[FileSymbolFields.Source].AsPath().Path, fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path)).ToArray()); | ||
| 59 | |||
| 60 | var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); | ||
| 61 | var fileRows = data.Tables["File"].Rows; | ||
| 62 | Assert.Equal(new[] | ||
| 63 | { | ||
| 64 | "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", | ||
| 65 | "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", | ||
| 66 | }, fileRows.Select(r => r.FieldAsString(0)).ToArray()); | ||
| 67 | |||
| 68 | var cabPath = Path.Combine(intermediateFolder, "msm-test.cab"); | ||
| 69 | Query.ExtractStream(msmPath, "MergeModule.CABinet", cabPath); | ||
| 70 | var files = Query.GetCabinetFiles(cabPath); | ||
| 71 | Assert.Equal(new[] | ||
| 72 | { | ||
| 73 | "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", | ||
| 74 | "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", | ||
| 75 | }, files.Select(f => Path.Combine(f.Path, f.Name)).ToArray()); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | [Fact] | ||
| 80 | public void CanSuppressModularization() | ||
| 81 | { | ||
| 82 | var folder = TestData.Get(@"TestData\SuppressModularization"); | ||
| 83 | |||
| 84 | using (var fs = new DisposableFileSystem()) | ||
| 85 | { | ||
| 86 | var intermediateFolder = fs.GetFolder(); | ||
| 87 | |||
| 88 | var result = WixRunner.Execute(new[] | ||
| 89 | { | ||
| 90 | "build", | ||
| 91 | Path.Combine(folder, "Module.wxs"), | ||
| 92 | "-loc", Path.Combine(folder, "Module.en-us.wxl"), | ||
| 93 | "-bindpath", Path.Combine(folder, "data"), | ||
| 94 | "-intermediateFolder", intermediateFolder, | ||
| 95 | "-sw1079", | ||
| 96 | "-sw1086", | ||
| 97 | "-o", Path.Combine(intermediateFolder, @"bin\test.msm") | ||
| 98 | }); | ||
| 99 | |||
| 100 | result.AssertSuccess(); | ||
| 101 | |||
| 102 | var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); | ||
| 103 | |||
| 104 | var rows = Query.QueryDatabase(msmPath, new[] { "CustomAction", "Property" }); | ||
| 105 | WixAssert.CompareLineByLine(new[] | ||
| 106 | { | ||
| 107 | "CustomAction:Test\t11265\tFakeCA.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tTestEntry\t", | ||
| 108 | "Property:MsiHiddenProperties\tTest" | ||
| 109 | }, rows); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs new file mode 100644 index 00000000..3bdfa0ef --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs | |||
| @@ -0,0 +1,838 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using Example.Extension; | ||
| 9 | using WixBuildTools.TestSupport; | ||
| 10 | using WixToolset.Core.TestPackage; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Data.WindowsInstaller; | ||
| 14 | using Xunit; | ||
| 15 | |||
| 16 | public class MsiFixture | ||
| 17 | { | ||
| 18 | [Fact] | ||
| 19 | public void CanBuildSingleFile() | ||
| 20 | { | ||
| 21 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 22 | |||
| 23 | using (var fs = new DisposableFileSystem()) | ||
| 24 | { | ||
| 25 | var baseFolder = fs.GetFolder(); | ||
| 26 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 27 | |||
| 28 | var result = WixRunner.Execute(new[] | ||
| 29 | { | ||
| 30 | "build", | ||
| 31 | Path.Combine(folder, "Package.wxs"), | ||
| 32 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 33 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 34 | "-bindpath", Path.Combine(folder, "data"), | ||
| 35 | "-intermediateFolder", intermediateFolder, | ||
| 36 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 37 | }); | ||
| 38 | |||
| 39 | result.AssertSuccess(); | ||
| 40 | |||
| 41 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); | ||
| 42 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 43 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); | ||
| 44 | |||
| 45 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 46 | |||
| 47 | Assert.False(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Compiled)); | ||
| 48 | Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Linked)); | ||
| 49 | Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Resolved)); | ||
| 50 | Assert.True(intermediate.HasLevel(WixToolset.Data.WindowsInstaller.IntermediateLevels.FullyBound)); | ||
| 51 | |||
| 52 | var section = intermediate.Sections.Single(); | ||
| 53 | |||
| 54 | var fileSymbol = section.Symbols.OfType<FileSymbol>().First(); | ||
| 55 | Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); | ||
| 56 | Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | [Fact] | ||
| 61 | public void CanBuildSingleFileCompressed() | ||
| 62 | { | ||
| 63 | var folder = TestData.Get(@"TestData\SingleFileCompressed"); | ||
| 64 | |||
| 65 | using (var fs = new DisposableFileSystem()) | ||
| 66 | { | ||
| 67 | var intermediateFolder = fs.GetFolder(); | ||
| 68 | |||
| 69 | var result = WixRunner.Execute(new[] | ||
| 70 | { | ||
| 71 | "build", | ||
| 72 | Path.Combine(folder, "Package.wxs"), | ||
| 73 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 74 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 75 | "-bindpath", Path.Combine(folder, "data"), | ||
| 76 | "-intermediateFolder", intermediateFolder, | ||
| 77 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 78 | }); | ||
| 79 | |||
| 80 | result.AssertSuccess(); | ||
| 81 | |||
| 82 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); | ||
| 83 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); | ||
| 84 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); | ||
| 85 | |||
| 86 | var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); | ||
| 87 | var section = intermediate.Sections.Single(); | ||
| 88 | |||
| 89 | var fileSymbol = section.Symbols.OfType<FileSymbol>().Single(); | ||
| 90 | Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); | ||
| 91 | Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | [Fact] | ||
| 96 | public void CanBuildSingleFileCompressedWithMediaTemplate() | ||
| 97 | { | ||
| 98 | var folder = TestData.Get(@"TestData\SingleFileCompressed"); | ||
| 99 | |||
| 100 | using (var fs = new DisposableFileSystem()) | ||
| 101 | { | ||
| 102 | var intermediateFolder = fs.GetFolder(); | ||
| 103 | |||
| 104 | var result = WixRunner.Execute(new[] | ||
| 105 | { | ||
| 106 | "build", | ||
| 107 | Path.Combine(folder, "Package.wxs"), | ||
| 108 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 109 | "-d", "MediaTemplateCompressionLevel", | ||
| 110 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 111 | "-bindpath", Path.Combine(folder, "data"), | ||
| 112 | "-intermediateFolder", intermediateFolder, | ||
| 113 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 114 | }); | ||
| 115 | |||
| 116 | result.AssertSuccess(); | ||
| 117 | |||
| 118 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); | ||
| 119 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); | ||
| 120 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | [Fact] | ||
| 125 | public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression() | ||
| 126 | { | ||
| 127 | var folder = TestData.Get(@"TestData\SingleFileCompressed"); | ||
| 128 | |||
| 129 | using (var fs = new DisposableFileSystem()) | ||
| 130 | { | ||
| 131 | var intermediateFolder = fs.GetFolder(); | ||
| 132 | |||
| 133 | var result = WixRunner.Execute(new[] | ||
| 134 | { | ||
| 135 | "build", | ||
| 136 | Path.Combine(folder, "Package.wxs"), | ||
| 137 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 138 | "-d", "MediaTemplateCompressionLevel=low", | ||
| 139 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 140 | "-bindpath", Path.Combine(folder, "data"), | ||
| 141 | "-intermediateFolder", intermediateFolder, | ||
| 142 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 143 | }); | ||
| 144 | |||
| 145 | result.AssertSuccess(); | ||
| 146 | |||
| 147 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); | ||
| 148 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab"))); | ||
| 149 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | [Fact] | ||
| 154 | public void CanBuildMultipleFilesCompressed() | ||
| 155 | { | ||
| 156 | var folder = TestData.Get(@"TestData\MultiFileCompressed"); | ||
| 157 | |||
| 158 | using (var fs = new DisposableFileSystem()) | ||
| 159 | { | ||
| 160 | var intermediateFolder = fs.GetFolder(); | ||
| 161 | |||
| 162 | var result = WixRunner.Execute(new[] | ||
| 163 | { | ||
| 164 | "build", | ||
| 165 | "-sw1079", // TODO: why does this test need to create a second cab which is empty? | ||
| 166 | Path.Combine(folder, "Package.wxs"), | ||
| 167 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 168 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 169 | "-bindpath", Path.Combine(folder, "data"), | ||
| 170 | "-intermediateFolder", intermediateFolder, | ||
| 171 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 172 | }); | ||
| 173 | |||
| 174 | result.AssertSuccess(); | ||
| 175 | |||
| 176 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); | ||
| 177 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example1.cab"))); | ||
| 178 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example2.cab"))); | ||
| 179 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | [Fact] | ||
| 184 | public void CanFailBuildMissingFile() | ||
| 185 | { | ||
| 186 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 187 | |||
| 188 | using (var fs = new DisposableFileSystem()) | ||
| 189 | { | ||
| 190 | var baseFolder = fs.GetFolder(); | ||
| 191 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 192 | |||
| 193 | var result = WixRunner.Execute(new[] | ||
| 194 | { | ||
| 195 | "build", | ||
| 196 | Path.Combine(folder, "Package.wxs"), | ||
| 197 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 198 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 199 | "-bindpath", Path.Combine(folder, "does-not-exist"), | ||
| 200 | "-bindpath", Path.Combine(folder, "also-does-not-exist"), | ||
| 201 | "-intermediateFolder", intermediateFolder, | ||
| 202 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 203 | }, out var messages); | ||
| 204 | Assert.Equal(103, result); | ||
| 205 | |||
| 206 | var error = messages.Single(m => m.Level == MessageLevel.Error); | ||
| 207 | var errorMessage = error.ToString(); | ||
| 208 | var checkedPaths = errorMessage.Substring(errorMessage.IndexOf(':') + 1).Split(new[] { ',' }).Select(s => s.Trim()).ToArray(); | ||
| 209 | Assert.Equal(new[] | ||
| 210 | { | ||
| 211 | "test.txt", | ||
| 212 | Path.Combine(folder, "does-not-exist", "test.txt"), | ||
| 213 | Path.Combine(folder, "also-does-not-exist", "test.txt"), | ||
| 214 | }, checkedPaths); | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | [Fact] | ||
| 219 | public void CanBuildWithErrorTable() | ||
| 220 | { | ||
| 221 | var folder = TestData.Get(@"TestData\ErrorsInUI"); | ||
| 222 | |||
| 223 | using (var fs = new DisposableFileSystem()) | ||
| 224 | { | ||
| 225 | var baseFolder = fs.GetFolder(); | ||
| 226 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 227 | |||
| 228 | var result = WixRunner.Execute(new[] | ||
| 229 | { | ||
| 230 | "build", | ||
| 231 | Path.Combine(folder, "Package.wxs"), | ||
| 232 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 233 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 234 | "-bindpath", Path.Combine(folder, "data"), | ||
| 235 | "-intermediateFolder", intermediateFolder, | ||
| 236 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 237 | }); | ||
| 238 | |||
| 239 | result.AssertSuccess(); | ||
| 240 | |||
| 241 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); | ||
| 242 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 243 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); | ||
| 244 | |||
| 245 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 246 | var section = intermediate.Sections.Single(); | ||
| 247 | |||
| 248 | var errors = section.Symbols.OfType<ErrorSymbol>().ToDictionary(t => t.Id.Id); | ||
| 249 | Assert.Equal("Category 55 Emergency Doomsday Crisis", errors["1234"].Message.Trim()); | ||
| 250 | Assert.Equal(" ", errors["5678"].Message); | ||
| 251 | |||
| 252 | var customAction1 = section.Symbols.OfType<CustomActionSymbol>().Where(t => t.Id.Id == "CanWeReferenceAnError_YesWeCan").Single(); | ||
| 253 | Assert.Equal("1234", customAction1.Target); | ||
| 254 | |||
| 255 | var customAction2 = section.Symbols.OfType<CustomActionSymbol>().Where(t => t.Id.Id == "TextErrorsWorkOKToo").Single(); | ||
| 256 | Assert.Equal("If you see this, something went wrong.", customAction2.Target); | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | [Fact] | ||
| 261 | public void CanLoadPdbGeneratedByBuild() | ||
| 262 | { | ||
| 263 | var folder = TestData.Get(@"TestData\MultiFileCompressed"); | ||
| 264 | |||
| 265 | using (var fs = new DisposableFileSystem()) | ||
| 266 | { | ||
| 267 | var intermediateFolder = fs.GetFolder(); | ||
| 268 | |||
| 269 | var result = WixRunner.Execute(new[] | ||
| 270 | { | ||
| 271 | "build", | ||
| 272 | Path.Combine(folder, "Package.wxs"), | ||
| 273 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 274 | "-d", "MediaTemplateCompressionLevel", | ||
| 275 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 276 | "-bindpath", Path.Combine(folder, "data"), | ||
| 277 | "-intermediateFolder", intermediateFolder, | ||
| 278 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 279 | }); | ||
| 280 | |||
| 281 | result.AssertSuccess(); | ||
| 282 | |||
| 283 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); | ||
| 284 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); | ||
| 285 | |||
| 286 | var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); | ||
| 287 | Assert.True(File.Exists(pdbPath)); | ||
| 288 | |||
| 289 | var output = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: true); | ||
| 290 | Assert.NotNull(output); | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | [Fact] | ||
| 295 | public void CanLoadPdbGeneratedByBuildViaWixOutput() | ||
| 296 | { | ||
| 297 | var folder = TestData.Get(@"TestData\MultiFileCompressed"); | ||
| 298 | |||
| 299 | using (var fs = new DisposableFileSystem()) | ||
| 300 | { | ||
| 301 | var intermediateFolder = fs.GetFolder(); | ||
| 302 | |||
| 303 | var result = WixRunner.Execute(new[] | ||
| 304 | { | ||
| 305 | "build", | ||
| 306 | Path.Combine(folder, "Package.wxs"), | ||
| 307 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 308 | "-d", "MediaTemplateCompressionLevel", | ||
| 309 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 310 | "-bindpath", Path.Combine(folder, "data"), | ||
| 311 | "-intermediateFolder", intermediateFolder, | ||
| 312 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 313 | }); | ||
| 314 | |||
| 315 | result.AssertSuccess(); | ||
| 316 | |||
| 317 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); | ||
| 318 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); | ||
| 319 | |||
| 320 | var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); | ||
| 321 | Assert.True(File.Exists(pdbPath)); | ||
| 322 | |||
| 323 | var wixOutput = WixOutput.Read(pdbPath); | ||
| 324 | var output = WindowsInstallerData.Load(wixOutput, suppressVersionCheck: true); | ||
| 325 | Assert.NotNull(output); | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | [Fact] | ||
| 330 | public void CanBuildManualUpgrade() | ||
| 331 | { | ||
| 332 | var folder = TestData.Get(@"TestData\ManualUpgrade"); | ||
| 333 | |||
| 334 | using (var fs = new DisposableFileSystem()) | ||
| 335 | { | ||
| 336 | var intermediateFolder = fs.GetFolder(); | ||
| 337 | |||
| 338 | var result = WixRunner.Execute(new[] | ||
| 339 | { | ||
| 340 | "build", | ||
| 341 | Path.Combine(folder, "Package.wxs"), | ||
| 342 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 343 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 344 | "-bindpath", Path.Combine(folder, "data"), | ||
| 345 | "-intermediateFolder", intermediateFolder, | ||
| 346 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 347 | }, out var messages); | ||
| 348 | |||
| 349 | Assert.Equal(0, result); | ||
| 350 | |||
| 351 | var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); | ||
| 352 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); | ||
| 353 | Assert.True(File.Exists(pdbPath)); | ||
| 354 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\test.txt"))); | ||
| 355 | |||
| 356 | var intermediate = Intermediate.Load(pdbPath); | ||
| 357 | var section = intermediate.Sections.Single(); | ||
| 358 | |||
| 359 | var upgradeSymbol = section.Symbols.OfType<UpgradeSymbol>().Single(); | ||
| 360 | Assert.False(upgradeSymbol.ExcludeLanguages); | ||
| 361 | Assert.True(upgradeSymbol.IgnoreRemoveFailures); | ||
| 362 | Assert.False(upgradeSymbol.VersionMaxInclusive); | ||
| 363 | Assert.True(upgradeSymbol.VersionMinInclusive); | ||
| 364 | Assert.Equal("13.0.0", upgradeSymbol.VersionMax); | ||
| 365 | Assert.Equal("12.0.0", upgradeSymbol.VersionMin); | ||
| 366 | Assert.False(upgradeSymbol.OnlyDetect); | ||
| 367 | Assert.Equal("BLAHBLAHBLAH", upgradeSymbol.ActionProperty); | ||
| 368 | |||
| 369 | var pdb = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: false); | ||
| 370 | var secureProperties = pdb.Tables["Property"].Rows.Where(row => row.GetKey() == "SecureCustomProperties").Single(); | ||
| 371 | Assert.Contains("BLAHBLAHBLAH", secureProperties.FieldAsString(1)); | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | [Fact] | ||
| 376 | public void CanBuildWixipl() | ||
| 377 | { | ||
| 378 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 379 | |||
| 380 | using (var fs = new DisposableFileSystem()) | ||
| 381 | { | ||
| 382 | var baseFolder = fs.GetFolder(); | ||
| 383 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 384 | |||
| 385 | var result = WixRunner.Execute(new[] | ||
| 386 | { | ||
| 387 | "build", | ||
| 388 | Path.Combine(folder, "Package.wxs"), | ||
| 389 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 390 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 391 | "-bindpath", Path.Combine(folder, "data"), | ||
| 392 | "-intermediateFolder", intermediateFolder, | ||
| 393 | "-o", Path.Combine(baseFolder, @"bin\test.wixipl") | ||
| 394 | }, out var messages); | ||
| 395 | |||
| 396 | Assert.Equal(0, result); | ||
| 397 | |||
| 398 | var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); | ||
| 399 | |||
| 400 | Assert.Equal(new[]{ | ||
| 401 | "test.wixipl" | ||
| 402 | }, builtFiles.Select(Path.GetFileName).ToArray()); | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | [Fact] | ||
| 407 | public void CanBuildWixlib() | ||
| 408 | { | ||
| 409 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 410 | |||
| 411 | using (var fs = new DisposableFileSystem()) | ||
| 412 | { | ||
| 413 | var baseFolder = fs.GetFolder(); | ||
| 414 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 415 | |||
| 416 | var result = WixRunner.Execute(new[] | ||
| 417 | { | ||
| 418 | "build", | ||
| 419 | Path.Combine(folder, "Package.wxs"), | ||
| 420 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 421 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 422 | "-bindpath", Path.Combine(folder, "data"), | ||
| 423 | "-intermediateFolder", intermediateFolder, | ||
| 424 | "-o", Path.Combine(baseFolder, @"bin\test.wixlib") | ||
| 425 | }, out var messages); | ||
| 426 | |||
| 427 | Assert.Equal(0, result); | ||
| 428 | |||
| 429 | var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); | ||
| 430 | |||
| 431 | Assert.Equal(new[]{ | ||
| 432 | "test.wixlib" | ||
| 433 | }, builtFiles.Select(Path.GetFileName).ToArray()); | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | [Fact] | ||
| 438 | public void CanBuildBinaryWixlib() | ||
| 439 | { | ||
| 440 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 441 | |||
| 442 | using (var fs = new DisposableFileSystem()) | ||
| 443 | { | ||
| 444 | var baseFolder = fs.GetFolder(); | ||
| 445 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 446 | |||
| 447 | var result = WixRunner.Execute( | ||
| 448 | "build", | ||
| 449 | Path.Combine(folder, "Package.wxs"), | ||
| 450 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 451 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 452 | "-bindpath", Path.Combine(folder, "data"), | ||
| 453 | "-intermediateFolder", intermediateFolder, | ||
| 454 | "-bindfiles", | ||
| 455 | "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); | ||
| 456 | |||
| 457 | result.AssertSuccess(); | ||
| 458 | |||
| 459 | using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) | ||
| 460 | { | ||
| 461 | Assert.NotNull(wixout.GetDataStream("wix-ir.json")); | ||
| 462 | |||
| 463 | var text = wixout.GetData("wix-ir/test.txt"); | ||
| 464 | Assert.Equal("This is test.txt.", text); | ||
| 465 | } | ||
| 466 | } | ||
| 467 | } | ||
| 468 | |||
| 469 | [Fact] | ||
| 470 | public void CanBuildBinaryWixlibWithCollidingFilenames() | ||
| 471 | { | ||
| 472 | var folder = TestData.Get(@"TestData\SameFileFolders"); | ||
| 473 | |||
| 474 | using (var fs = new DisposableFileSystem()) | ||
| 475 | { | ||
| 476 | var baseFolder = fs.GetFolder(); | ||
| 477 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 478 | |||
| 479 | var result = WixRunner.Execute( | ||
| 480 | "build", | ||
| 481 | Path.Combine(folder, "TestComponents.wxs"), | ||
| 482 | "-bindpath", Path.Combine(folder, "data"), | ||
| 483 | "-intermediateFolder", intermediateFolder, | ||
| 484 | "-bindfiles", | ||
| 485 | "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); | ||
| 486 | |||
| 487 | result.AssertSuccess(); | ||
| 488 | |||
| 489 | using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) | ||
| 490 | { | ||
| 491 | Assert.NotNull(wixout.GetDataStream("wix-ir.json")); | ||
| 492 | |||
| 493 | var text = wixout.GetData("wix-ir/test.txt"); | ||
| 494 | Assert.Equal(@"This is a\test.txt.", text); | ||
| 495 | |||
| 496 | var text2 = wixout.GetData("wix-ir/test.txt-1"); | ||
| 497 | Assert.Equal(@"This is b\test.txt.", text2); | ||
| 498 | |||
| 499 | var text3 = wixout.GetData("wix-ir/test.txt-2"); | ||
| 500 | Assert.Equal(@"This is c\test.txt.", text3); | ||
| 501 | } | ||
| 502 | } | ||
| 503 | } | ||
| 504 | |||
| 505 | [Fact] | ||
| 506 | public void CanBuildWithIncludePath() | ||
| 507 | { | ||
| 508 | var folder = TestData.Get(@"TestData\IncludePath"); | ||
| 509 | var bindpath = Path.Combine(folder, "data"); | ||
| 510 | |||
| 511 | using (var fs = new DisposableFileSystem()) | ||
| 512 | { | ||
| 513 | var baseFolder = fs.GetFolder(); | ||
| 514 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 515 | |||
| 516 | var result = WixRunner.Execute( | ||
| 517 | "build", | ||
| 518 | Path.Combine(folder, "Package.wxs"), | ||
| 519 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 520 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 521 | "-bindpath", bindpath, | ||
| 522 | "-intermediateFolder", intermediateFolder, | ||
| 523 | "-o", Path.Combine(baseFolder, @"bin\test.msi"), | ||
| 524 | "-i", bindpath); | ||
| 525 | |||
| 526 | result.AssertSuccess(); | ||
| 527 | |||
| 528 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); | ||
| 529 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 530 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); | ||
| 531 | |||
| 532 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 533 | var section = intermediate.Sections.Single(); | ||
| 534 | |||
| 535 | var fileSymbol = section.Symbols.OfType<FileSymbol>().Single(); | ||
| 536 | Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); | ||
| 537 | Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 538 | } | ||
| 539 | } | ||
| 540 | |||
| 541 | [Fact] | ||
| 542 | public void CanBuildWithAssembly() | ||
| 543 | { | ||
| 544 | var folder = TestData.Get(@"TestData\Assembly"); | ||
| 545 | |||
| 546 | using (var fs = new DisposableFileSystem()) | ||
| 547 | { | ||
| 548 | var baseFolder = fs.GetFolder(); | ||
| 549 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 550 | |||
| 551 | var result = WixRunner.Execute(new[] | ||
| 552 | { | ||
| 553 | "build", | ||
| 554 | Path.Combine(folder, "Package.wxs"), | ||
| 555 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 556 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 557 | "-bindpath", Path.Combine(folder, "data"), | ||
| 558 | "-intermediateFolder", intermediateFolder, | ||
| 559 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 560 | }); | ||
| 561 | |||
| 562 | result.AssertSuccess(); | ||
| 563 | |||
| 564 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); | ||
| 565 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 566 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\AssemblyMsiPackage\candle.exe"))); | ||
| 567 | |||
| 568 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 569 | var section = intermediate.Sections.Single(); | ||
| 570 | |||
| 571 | var fileSymbol = section.Symbols.OfType<FileSymbol>().Single(); | ||
| 572 | Assert.Equal(Path.Combine(folder, @"data\candle.exe"), fileSymbol[FileSymbolFields.Source].AsPath().Path); | ||
| 573 | Assert.Equal(@"candle.exe", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 574 | |||
| 575 | var msiAssemblyNameSymbols = section.Symbols.OfType<MsiAssemblyNameSymbol>(); | ||
| 576 | Assert.Equal(new[] | ||
| 577 | { | ||
| 578 | "culture", | ||
| 579 | "fileVersion", | ||
| 580 | "name", | ||
| 581 | "processorArchitecture", | ||
| 582 | "publicKeyToken", | ||
| 583 | "version" | ||
| 584 | }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Name).ToArray()); | ||
| 585 | |||
| 586 | Assert.Equal(new[] | ||
| 587 | { | ||
| 588 | "neutral", | ||
| 589 | "3.11.11810.0", | ||
| 590 | "candle", | ||
| 591 | "x86", | ||
| 592 | "256B3414DFA97718", | ||
| 593 | "3.0.0.0" | ||
| 594 | }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Value).ToArray()); | ||
| 595 | } | ||
| 596 | } | ||
| 597 | |||
| 598 | [Fact] | ||
| 599 | public void CanBuild64bit() | ||
| 600 | { | ||
| 601 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 602 | |||
| 603 | using (var fs = new DisposableFileSystem()) | ||
| 604 | { | ||
| 605 | var baseFolder = fs.GetFolder(); | ||
| 606 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 607 | |||
| 608 | var result = WixRunner.Execute(new[] | ||
| 609 | { | ||
| 610 | "build", | ||
| 611 | Path.Combine(folder, "Package.wxs"), | ||
| 612 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 613 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 614 | "-bindpath", Path.Combine(folder, "data"), | ||
| 615 | "-intermediateFolder", intermediateFolder, | ||
| 616 | "-arch", "x64", | ||
| 617 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 618 | }); | ||
| 619 | |||
| 620 | result.AssertSuccess(); | ||
| 621 | |||
| 622 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 623 | var section = intermediate.Sections.Single(); | ||
| 624 | |||
| 625 | var platformSummary = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); | ||
| 626 | Assert.Equal("x64;1033", platformSummary.Value); | ||
| 627 | } | ||
| 628 | } | ||
| 629 | |||
| 630 | [Fact] | ||
| 631 | public void CanBuildSharedComponent() | ||
| 632 | { | ||
| 633 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 634 | |||
| 635 | using (var fs = new DisposableFileSystem()) | ||
| 636 | { | ||
| 637 | var baseFolder = fs.GetFolder(); | ||
| 638 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 639 | |||
| 640 | var result = WixRunner.Execute(new[] | ||
| 641 | { | ||
| 642 | "build", | ||
| 643 | Path.Combine(folder, "Package.wxs"), | ||
| 644 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 645 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 646 | "-bindpath", Path.Combine(folder, "data"), | ||
| 647 | "-intermediateFolder", intermediateFolder, | ||
| 648 | "-arch", "x64", | ||
| 649 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 650 | }); | ||
| 651 | |||
| 652 | result.AssertSuccess(); | ||
| 653 | |||
| 654 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 655 | var section = intermediate.Sections.Single(); | ||
| 656 | |||
| 657 | // Only one component is shared. | ||
| 658 | var sharedComponentSymbols = section.Symbols.OfType<ComponentSymbol>(); | ||
| 659 | Assert.Equal(1, sharedComponentSymbols.Sum(t => t.Shared ? 1 : 0)); | ||
| 660 | |||
| 661 | // And it is this one. | ||
| 662 | var sharedComponentSymbol = sharedComponentSymbols.Single(t => t.Id.Id == "Shared.dll"); | ||
| 663 | Assert.True(sharedComponentSymbol.Shared); | ||
| 664 | } | ||
| 665 | } | ||
| 666 | |||
| 667 | [Fact] | ||
| 668 | public void CanBuildSetProperty() | ||
| 669 | { | ||
| 670 | var folder = TestData.Get(@"TestData\SetProperty"); | ||
| 671 | |||
| 672 | using (var fs = new DisposableFileSystem()) | ||
| 673 | { | ||
| 674 | var baseFolder = fs.GetFolder(); | ||
| 675 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 676 | |||
| 677 | var result = WixRunner.Execute(new[] | ||
| 678 | { | ||
| 679 | "build", | ||
| 680 | Path.Combine(folder, "Package.wxs"), | ||
| 681 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 682 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 683 | "-bindpath", Path.Combine(folder, "data"), | ||
| 684 | "-intermediateFolder", intermediateFolder, | ||
| 685 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 686 | }); | ||
| 687 | |||
| 688 | result.AssertSuccess(); | ||
| 689 | |||
| 690 | var output = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"), false); | ||
| 691 | var caRows = output.Tables["CustomAction"].Rows.Single(); | ||
| 692 | Assert.Equal("SetINSTALLLOCATION", caRows.FieldAsString(0)); | ||
| 693 | Assert.Equal("51", caRows.FieldAsString(1)); | ||
| 694 | Assert.Equal("INSTALLLOCATION", caRows.FieldAsString(2)); | ||
| 695 | Assert.Equal("[INSTALLFOLDER]", caRows.FieldAsString(3)); | ||
| 696 | } | ||
| 697 | } | ||
| 698 | |||
| 699 | [Fact] | ||
| 700 | public void CanBuildVersionIndependentProgId() | ||
| 701 | { | ||
| 702 | var folder = TestData.Get(@"TestData\ProgId"); | ||
| 703 | |||
| 704 | using (var fs = new DisposableFileSystem()) | ||
| 705 | { | ||
| 706 | var baseFolder = fs.GetFolder(); | ||
| 707 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 708 | |||
| 709 | var result = WixRunner.Execute(new[] | ||
| 710 | { | ||
| 711 | "build", | ||
| 712 | Path.Combine(folder, "Package.wxs"), | ||
| 713 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 714 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 715 | "-bindpath", Path.Combine(folder, "data"), | ||
| 716 | "-intermediateFolder", intermediateFolder, | ||
| 717 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 718 | }); | ||
| 719 | |||
| 720 | result.AssertSuccess(); | ||
| 721 | |||
| 722 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); | ||
| 723 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 724 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\Foo.exe"))); | ||
| 725 | |||
| 726 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 727 | var section = intermediate.Sections.Single(); | ||
| 728 | |||
| 729 | var progids = section.Symbols.OfType<ProgIdSymbol>().OrderBy(symbol => symbol.ProgId).ToList(); | ||
| 730 | Assert.Equal(new[] | ||
| 731 | { | ||
| 732 | "Foo.File.hol", | ||
| 733 | "Foo.File.hol.15" | ||
| 734 | }, progids.Select(p => p.ProgId).ToArray()); | ||
| 735 | |||
| 736 | Assert.Equal(new[] | ||
| 737 | { | ||
| 738 | "Foo.File.hol.15", | ||
| 739 | null | ||
| 740 | }, progids.Select(p => p.ParentProgIdRef).ToArray()); | ||
| 741 | } | ||
| 742 | } | ||
| 743 | |||
| 744 | [Fact] | ||
| 745 | public void CanBuildInstanceTransform() | ||
| 746 | { | ||
| 747 | var folder = TestData.Get(@"TestData\InstanceTransform"); | ||
| 748 | |||
| 749 | using (var fs = new DisposableFileSystem()) | ||
| 750 | { | ||
| 751 | var intermediateFolder = fs.GetFolder(); | ||
| 752 | |||
| 753 | var result = WixRunner.Execute(new[] | ||
| 754 | { | ||
| 755 | "build", | ||
| 756 | Path.Combine(folder, "Package.wxs"), | ||
| 757 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 758 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 759 | "-bindpath", Path.Combine(folder, "data"), | ||
| 760 | "-intermediateFolder", intermediateFolder, | ||
| 761 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 762 | }); | ||
| 763 | |||
| 764 | result.AssertSuccess(); | ||
| 765 | |||
| 766 | var output = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"), false); | ||
| 767 | var substorage = output.SubStorages.Single(); | ||
| 768 | Assert.Equal("I1", substorage.Name); | ||
| 769 | |||
| 770 | var data = substorage.Data; | ||
| 771 | Assert.Equal(new[] | ||
| 772 | { | ||
| 773 | "_SummaryInformation", | ||
| 774 | "Property", | ||
| 775 | "Upgrade" | ||
| 776 | }, data.Tables.Select(t => t.Name).ToArray()); | ||
| 777 | |||
| 778 | Assert.Equal(new[] | ||
| 779 | { | ||
| 780 | "INSTANCEPROPERTY\tI1", | ||
| 781 | "ProductName\tMsiPackage (Instance 1)", | ||
| 782 | }, JoinRows(data.Tables["Property"])); | ||
| 783 | |||
| 784 | Assert.Equal(new[] | ||
| 785 | { | ||
| 786 | "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", | ||
| 787 | "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t0\t0", | ||
| 788 | "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", | ||
| 789 | "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t0\t0" | ||
| 790 | }, JoinRows(data.Tables["Upgrade"])); | ||
| 791 | } | ||
| 792 | } | ||
| 793 | |||
| 794 | [Fact(Skip = "Test demonstrates failure")] | ||
| 795 | public void FailsBuildAtLinkTimeForMissingEnsureTable() | ||
| 796 | { | ||
| 797 | var folder = TestData.Get(@"TestData"); | ||
| 798 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 799 | |||
| 800 | using (var fs = new DisposableFileSystem()) | ||
| 801 | { | ||
| 802 | var baseFolder = fs.GetFolder(); | ||
| 803 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 804 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 805 | |||
| 806 | var result = WixRunner.Execute(new[] | ||
| 807 | { | ||
| 808 | "build", | ||
| 809 | Path.Combine(folder, "BadEnsureTable", "BadEnsureTable.wxs"), | ||
| 810 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 811 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 812 | "-ext", extensionPath, | ||
| 813 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 814 | "-intermediateFolder", intermediateFolder, | ||
| 815 | "-o", msiPath | ||
| 816 | }); | ||
| 817 | Assert.Collection(result.Messages, | ||
| 818 | first => | ||
| 819 | { | ||
| 820 | Assert.Equal(MessageLevel.Error, first.Level); | ||
| 821 | Assert.Equal("The identifier 'WixCustomTable:TableDefinitionNotExposedByExtension' could not be found. Ensure you have typed the reference correctly and that all the necessary inputs are provided to the linker.", first.ToString()); | ||
| 822 | }); | ||
| 823 | |||
| 824 | Assert.False(File.Exists(msiPath)); | ||
| 825 | } | ||
| 826 | } | ||
| 827 | |||
| 828 | private static string[] JoinRows(Table table) | ||
| 829 | { | ||
| 830 | return table.Rows.Select(r => JoinFields(r.Fields)).ToArray(); | ||
| 831 | |||
| 832 | string JoinFields(Field[] fields) | ||
| 833 | { | ||
| 834 | return String.Join('\t', fields.Select(f => f.ToString())); | ||
| 835 | } | ||
| 836 | } | ||
| 837 | } | ||
| 838 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs new file mode 100644 index 00000000..71edddc6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | |||
| @@ -0,0 +1,1040 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using Example.Extension; | ||
| 9 | using WixBuildTools.TestSupport; | ||
| 10 | using WixToolset.Core.TestPackage; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using WixToolset.Data.WindowsInstaller; | ||
| 14 | using Xunit; | ||
| 15 | |||
| 16 | public class MsiQueryFixture | ||
| 17 | { | ||
| 18 | [Fact] | ||
| 19 | public void PopulatesAppIdTableWhenAdvertised() | ||
| 20 | { | ||
| 21 | var folder = TestData.Get(@"TestData"); | ||
| 22 | |||
| 23 | using (var fs = new DisposableFileSystem()) | ||
| 24 | { | ||
| 25 | var baseFolder = fs.GetFolder(); | ||
| 26 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 27 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 28 | |||
| 29 | var result = WixRunner.Execute(new[] | ||
| 30 | { | ||
| 31 | "build", | ||
| 32 | Path.Combine(folder, "AppId", "Advertised.wxs"), | ||
| 33 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 34 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 35 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 36 | "-intermediateFolder", intermediateFolder, | ||
| 37 | "-o", msiPath | ||
| 38 | }); | ||
| 39 | |||
| 40 | result.AssertSuccess(); | ||
| 41 | |||
| 42 | Assert.True(File.Exists(msiPath)); | ||
| 43 | var results = Query.QueryDatabase(msiPath, new[] { "AppId" }); | ||
| 44 | WixAssert.CompareLineByLine(new[] | ||
| 45 | { | ||
| 46 | "AppId:{D6040299-B15C-4C94-AE26-0C9B60D14C35}\t\t\t\t\t\t", | ||
| 47 | }, results); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | [Fact] | ||
| 52 | public void PopulatesAppSearchTablesFromComponentSearch() | ||
| 53 | { | ||
| 54 | var folder = TestData.Get(@"TestData"); | ||
| 55 | |||
| 56 | using (var fs = new DisposableFileSystem()) | ||
| 57 | { | ||
| 58 | var baseFolder = fs.GetFolder(); | ||
| 59 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 60 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 61 | |||
| 62 | var result = WixRunner.Execute(new[] | ||
| 63 | { | ||
| 64 | "build", | ||
| 65 | Path.Combine(folder, "AppSearch", "ComponentSearch.wxs"), | ||
| 66 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 67 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 68 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 69 | "-intermediateFolder", intermediateFolder, | ||
| 70 | "-o", msiPath | ||
| 71 | }); | ||
| 72 | |||
| 73 | result.AssertSuccess(); | ||
| 74 | |||
| 75 | Assert.True(File.Exists(msiPath)); | ||
| 76 | var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "CompLocator" }); | ||
| 77 | WixAssert.CompareLineByLine(new[] | ||
| 78 | { | ||
| 79 | "AppSearch:SAMPLECOMPFOUND\tSampleCompSearch", | ||
| 80 | "CompLocator:SampleCompSearch\t{4D9A0D20-D0CC-40DE-B580-EAD38B985217}\t1", | ||
| 81 | }, results); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | [Fact] | ||
| 86 | public void PopulatesAppSearchTablesFromDirectorySearch() | ||
| 87 | { | ||
| 88 | var folder = TestData.Get(@"TestData"); | ||
| 89 | |||
| 90 | using (var fs = new DisposableFileSystem()) | ||
| 91 | { | ||
| 92 | var baseFolder = fs.GetFolder(); | ||
| 93 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 94 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 95 | |||
| 96 | var result = WixRunner.Execute(new[] | ||
| 97 | { | ||
| 98 | "build", | ||
| 99 | Path.Combine(folder, "AppSearch", "DirectorySearch.wxs"), | ||
| 100 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 101 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 102 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 103 | "-intermediateFolder", intermediateFolder, | ||
| 104 | "-o", msiPath | ||
| 105 | }); | ||
| 106 | |||
| 107 | result.AssertSuccess(); | ||
| 108 | |||
| 109 | Assert.True(File.Exists(msiPath)); | ||
| 110 | var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator" }); | ||
| 111 | WixAssert.CompareLineByLine(new[] | ||
| 112 | { | ||
| 113 | "AppSearch:SAMPLEDIRFOUND\tSampleDirSearch", | ||
| 114 | "DrLocator:SampleDirSearch\t\tC:\\SampleDir\t", | ||
| 115 | }, results); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | [Fact] | ||
| 120 | public void PopulatesAppSearchTablesFromFileSearch() | ||
| 121 | { | ||
| 122 | var folder = TestData.Get(@"TestData"); | ||
| 123 | |||
| 124 | using (var fs = new DisposableFileSystem()) | ||
| 125 | { | ||
| 126 | var baseFolder = fs.GetFolder(); | ||
| 127 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 128 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 129 | |||
| 130 | var result = WixRunner.Execute(new[] | ||
| 131 | { | ||
| 132 | "build", | ||
| 133 | Path.Combine(folder, "AppSearch", "FileSearch.wxs"), | ||
| 134 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 135 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 136 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 137 | "-intermediateFolder", intermediateFolder, | ||
| 138 | "-o", msiPath | ||
| 139 | }); | ||
| 140 | |||
| 141 | result.AssertSuccess(); | ||
| 142 | |||
| 143 | Assert.True(File.Exists(msiPath)); | ||
| 144 | var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator", "IniLocator" }); | ||
| 145 | WixAssert.CompareLineByLine(new[] | ||
| 146 | { | ||
| 147 | "AppSearch:SAMPLEFILEFOUND\tSampleFileSearch", | ||
| 148 | "DrLocator:SampleFileSearch\tSampleIniFileSearch\t\t", | ||
| 149 | "IniLocator:SampleFileSearch\tsample.fil\tMySection\tMyKey\t\t1", | ||
| 150 | }, results); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | [Fact] | ||
| 155 | public void PopulatesAppSearchTablesFromRegistrySearch() | ||
| 156 | { | ||
| 157 | var folder = TestData.Get(@"TestData"); | ||
| 158 | |||
| 159 | using (var fs = new DisposableFileSystem()) | ||
| 160 | { | ||
| 161 | var baseFolder = fs.GetFolder(); | ||
| 162 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 163 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 164 | |||
| 165 | var result = WixRunner.Execute(new[] | ||
| 166 | { | ||
| 167 | "build", | ||
| 168 | Path.Combine(folder, "AppSearch", "RegistrySearch.wxs"), | ||
| 169 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 170 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 171 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 172 | "-intermediateFolder", intermediateFolder, | ||
| 173 | "-o", msiPath | ||
| 174 | }); | ||
| 175 | |||
| 176 | result.AssertSuccess(); | ||
| 177 | |||
| 178 | Assert.True(File.Exists(msiPath)); | ||
| 179 | var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); | ||
| 180 | WixAssert.CompareLineByLine(new[] | ||
| 181 | { | ||
| 182 | "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", | ||
| 183 | "RegLocator:SampleRegSearch\t2\tSampleReg\t\t2", | ||
| 184 | }, results); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | [Fact] | ||
| 189 | public void PopulatesAppSearchTablesFromRegistrySearch64() | ||
| 190 | { | ||
| 191 | var folder = TestData.Get(@"TestData"); | ||
| 192 | |||
| 193 | using (var fs = new DisposableFileSystem()) | ||
| 194 | { | ||
| 195 | var baseFolder = fs.GetFolder(); | ||
| 196 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 197 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 198 | |||
| 199 | var result = WixRunner.Execute(new[] | ||
| 200 | { | ||
| 201 | "build", | ||
| 202 | Path.Combine(folder, "AppSearch", "RegistrySearch64.wxs"), | ||
| 203 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 204 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 205 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 206 | "-intermediateFolder", intermediateFolder, | ||
| 207 | "-o", msiPath | ||
| 208 | }); | ||
| 209 | |||
| 210 | result.AssertSuccess(); | ||
| 211 | |||
| 212 | Assert.True(File.Exists(msiPath)); | ||
| 213 | var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); | ||
| 214 | WixAssert.CompareLineByLine(new[] | ||
| 215 | { | ||
| 216 | "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", | ||
| 217 | "RegLocator:SampleRegSearch\t2\tSampleReg\t\t18", | ||
| 218 | }, results); | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | [Fact] | ||
| 223 | public void PopulatesClassTablesWhenIconIndexIsZero() | ||
| 224 | { | ||
| 225 | var folder = TestData.Get(@"TestData"); | ||
| 226 | |||
| 227 | using (var fs = new DisposableFileSystem()) | ||
| 228 | { | ||
| 229 | var baseFolder = fs.GetFolder(); | ||
| 230 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 231 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 232 | |||
| 233 | var result = WixRunner.Execute(new[] | ||
| 234 | { | ||
| 235 | "build", | ||
| 236 | Path.Combine(folder, "Class", "IconIndex0.wxs"), | ||
| 237 | Path.Combine(folder, "Icon", "SampleIcon.wxs"), | ||
| 238 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 239 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 240 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 241 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 242 | "-intermediateFolder", intermediateFolder, | ||
| 243 | "-o", msiPath | ||
| 244 | }); | ||
| 245 | |||
| 246 | result.AssertSuccess(); | ||
| 247 | |||
| 248 | Assert.True(File.Exists(msiPath)); | ||
| 249 | var results = Query.QueryDatabase(msiPath, new[] { "Class" }); | ||
| 250 | WixAssert.CompareLineByLine(new[] | ||
| 251 | { | ||
| 252 | "Class:{3FAED4CC-C473-4B8A-BE8B-303871377A4A}\tLocalServer32\tClassComp\t\tFakeClass3FAE\t\t\tSampleIcon\t0\t\t\tProductFeature\t", | ||
| 253 | }, results); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | [Fact] | ||
| 258 | public void PopulatesClassTablesWhenProgIdIsNestedUnderAdvertisedClass() | ||
| 259 | { | ||
| 260 | var folder = TestData.Get(@"TestData"); | ||
| 261 | |||
| 262 | using (var fs = new DisposableFileSystem()) | ||
| 263 | { | ||
| 264 | var baseFolder = fs.GetFolder(); | ||
| 265 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 266 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 267 | |||
| 268 | var result = WixRunner.Execute(new[] | ||
| 269 | { | ||
| 270 | "build", | ||
| 271 | Path.Combine(folder, "ProgId", "NestedUnderClass.wxs"), | ||
| 272 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 273 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 274 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 275 | "-intermediateFolder", intermediateFolder, | ||
| 276 | "-o", msiPath | ||
| 277 | }); | ||
| 278 | |||
| 279 | result.AssertSuccess(); | ||
| 280 | |||
| 281 | Assert.True(File.Exists(msiPath)); | ||
| 282 | var results = Query.QueryDatabase(msiPath, new[] { "Class", "ProgId", "Registry" }); | ||
| 283 | WixAssert.CompareLineByLine(new[] | ||
| 284 | { | ||
| 285 | "Class:{F12A6F69-117F-471F-AE73-F8E74218F498}\tLocalServer32\tProgIdComp\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tFakeClassF12A\t\t\t\t\t\t\tProductFeature\t", | ||
| 286 | "ProgId:73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\t\t{F12A6F69-117F-471F-AE73-F8E74218F498}\tFakeClassF12A\t\t", | ||
| 287 | "Registry:regUIIK326nDZpkWHuexeF58EikQvA\t0\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tNoOpen\tNoOpen73E7\tProgIdComp", | ||
| 288 | "Registry:regvrhMurMp98anbQJkpgA8yJCefdM\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\Version\t\t0.0.0.1\tProgIdComp", | ||
| 289 | "Registry:regY1F4E2lvu_Up6gV6c3jeN5ukn8s\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\LocalServer32\tThreadingModel\tApartment\tProgIdComp", | ||
| 290 | }, results); | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | [Fact] | ||
| 295 | public void PopulatesControlTables() | ||
| 296 | { | ||
| 297 | var folder = TestData.Get(@"TestData"); | ||
| 298 | |||
| 299 | using (var fs = new DisposableFileSystem()) | ||
| 300 | { | ||
| 301 | var baseFolder = fs.GetFolder(); | ||
| 302 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 303 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 304 | |||
| 305 | var result = WixRunner.Execute(new[] | ||
| 306 | { | ||
| 307 | "build", | ||
| 308 | Path.Combine(folder, "DialogsInInstallUISequence", "PackageComponents.wxs"), | ||
| 309 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 310 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 311 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 312 | "-intermediateFolder", intermediateFolder, | ||
| 313 | "-o", msiPath, | ||
| 314 | }); | ||
| 315 | |||
| 316 | result.AssertSuccess(); | ||
| 317 | |||
| 318 | Assert.True(File.Exists(msiPath)); | ||
| 319 | |||
| 320 | var results = Query.QueryDatabase(msiPath, new[] { "CheckBox", "Control", "ControlCondition", "InstallUISequence" }); | ||
| 321 | WixAssert.CompareLineByLine(new[] | ||
| 322 | { | ||
| 323 | "CheckBox:WIXUI_EXITDIALOGOPTIONALCHECKBOX\t1", | ||
| 324 | "Control:FirstDialog\tHeader\tText\t0\t13\t90\t13\t3\t\tFirstDialogHeader\tTitle\t", | ||
| 325 | "Control:FirstDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tFirstDialogTitle\tHeader\t", | ||
| 326 | "Control:SecondDialog\tOptionalCheckBox\tCheckBox\t0\t13\t100\t40\t2\tWIXUI_EXITDIALOGOPTIONALCHECKBOX\t[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]\tTitle\tOptional checkbox|Check this box for fun", | ||
| 327 | "Control:SecondDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tSecondDialogTitle\tOptionalCheckBox\t", | ||
| 328 | "ControlCondition:FirstDialog\tHeader\tDisable\tInstalled", | ||
| 329 | "ControlCondition:FirstDialog\tHeader\tHide\tInstalled", | ||
| 330 | "ControlCondition:SecondDialog\tOptionalCheckBox\tShow\tWIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT AND NOT Installed", | ||
| 331 | "InstallUISequence:CostFinalize\t\t1000", | ||
| 332 | "InstallUISequence:CostInitialize\t\t800", | ||
| 333 | "InstallUISequence:ExecuteAction\t\t1300", | ||
| 334 | "InstallUISequence:FileCost\t\t900", | ||
| 335 | "InstallUISequence:FindRelatedProducts\t\t25", | ||
| 336 | "InstallUISequence:FirstDialog\tInstalled AND PATCH\t1298", | ||
| 337 | "InstallUISequence:LaunchConditions\t\t100", | ||
| 338 | "InstallUISequence:MigrateFeatureStates\t\t1200", | ||
| 339 | "InstallUISequence:SecondDialog\tNOT Installed\t1299", | ||
| 340 | "InstallUISequence:ValidateProductID\t\t700", | ||
| 341 | }, results); | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | [Fact] | ||
| 346 | public void PopulatesCreateFolderTableForNullKeypathComponents() | ||
| 347 | { | ||
| 348 | var folder = TestData.Get(@"TestData\Components"); | ||
| 349 | |||
| 350 | using (var fs = new DisposableFileSystem()) | ||
| 351 | { | ||
| 352 | var baseFolder = fs.GetFolder(); | ||
| 353 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 354 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 355 | |||
| 356 | var result = WixRunner.Execute(new[] | ||
| 357 | { | ||
| 358 | "build", | ||
| 359 | Path.Combine(folder, "Package.wxs"), | ||
| 360 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 361 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 362 | "-bindpath", Path.Combine(folder, "data"), | ||
| 363 | "-intermediateFolder", intermediateFolder, | ||
| 364 | "-o", msiPath | ||
| 365 | }); | ||
| 366 | |||
| 367 | result.AssertSuccess(); | ||
| 368 | |||
| 369 | Assert.True(File.Exists(msiPath)); | ||
| 370 | var results = Query.QueryDatabase(msiPath, new[] { "CreateFolder" }); | ||
| 371 | WixAssert.CompareLineByLine(new[] | ||
| 372 | { | ||
| 373 | "CreateFolder:INSTALLFOLDER\tNullKeypathComponent", | ||
| 374 | }, results); | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | [Fact] | ||
| 379 | public void PopulatesDirectoryTableWithValidDefaultDir() | ||
| 380 | { | ||
| 381 | var folder = TestData.Get(@"TestData"); | ||
| 382 | |||
| 383 | using (var fs = new DisposableFileSystem()) | ||
| 384 | { | ||
| 385 | var baseFolder = fs.GetFolder(); | ||
| 386 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 387 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 388 | |||
| 389 | var result = WixRunner.Execute(new[] | ||
| 390 | { | ||
| 391 | "build", | ||
| 392 | "-sw1031", // this is expected for this test | ||
| 393 | Path.Combine(folder, "DefaultDir", "DefaultDir.wxs"), | ||
| 394 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 395 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 396 | "-intermediateFolder", intermediateFolder, | ||
| 397 | "-o", msiPath | ||
| 398 | }); | ||
| 399 | |||
| 400 | result.AssertSuccess(); | ||
| 401 | |||
| 402 | Assert.True(File.Exists(msiPath)); | ||
| 403 | var results = Query.QueryDatabase(msiPath, new[] { "Directory" }); | ||
| 404 | WixAssert.CompareLineByLine(new[] | ||
| 405 | { | ||
| 406 | "Directory:DUPLICATENAMEANDSHORTNAME\tINSTALLFOLDER\tduplicat", | ||
| 407 | "Directory:Folder1\tINSTALLFOLDER\tFolder.1", | ||
| 408 | "Directory:Folder12\tINSTALLFOLDER\tFolder.12", | ||
| 409 | "Directory:Folder123\tINSTALLFOLDER\tFolder.123", | ||
| 410 | "Directory:Folder1234\tINSTALLFOLDER\tyakwclwy|Folder.1234", | ||
| 411 | "Directory:INSTALLFOLDER\tProgramFiles6432Folder\t1egc1laj|MsiPackage", | ||
| 412 | "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName", | ||
| 413 | "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName", | ||
| 414 | "Directory:NAMEWITHSHORTVALUE\tINSTALLFOLDER\tSHORTVAL", | ||
| 415 | "Directory:ProgramFiles6432Folder\tProgramFilesFolder\t.", | ||
| 416 | "Directory:ProgramFilesFolder\tTARGETDIR\tPFiles", | ||
| 417 | "Directory:SHORTNAMEANDLONGSOURCENAME\tINSTALLFOLDER\tSHNALSNM:6ukthv5q|ShortNameAndLongSourceName", | ||
| 418 | "Directory:SHORTNAMEONLY\tINSTALLFOLDER\tSHORTONL", | ||
| 419 | "Directory:SOURCENAME\tINSTALLFOLDER\ts2s5bq-i|NameAndSourceName:dhnqygng|SourceNameWithName", | ||
| 420 | "Directory:SOURCENAMESONLY\tINSTALLFOLDER\t.:SRCNAMON|SourceNameOnly", | ||
| 421 | "Directory:SOURCENAMEWITHSHORTVALUE\tINSTALLFOLDER\t.:SRTSRCVL", | ||
| 422 | "Directory:TARGETDIR\t\tSourceDir", | ||
| 423 | }, results); | ||
| 424 | } | ||
| 425 | } | ||
| 426 | |||
| 427 | [Fact] | ||
| 428 | public void PopulatesEnvironmentTable() | ||
| 429 | { | ||
| 430 | var folder = TestData.Get(@"TestData"); | ||
| 431 | |||
| 432 | using (var fs = new DisposableFileSystem()) | ||
| 433 | { | ||
| 434 | var baseFolder = fs.GetFolder(); | ||
| 435 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 436 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 437 | |||
| 438 | var result = WixRunner.Execute(new[] | ||
| 439 | { | ||
| 440 | "build", | ||
| 441 | Path.Combine(folder, "Environment", "Environment.wxs"), | ||
| 442 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 443 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 444 | "-intermediateFolder", intermediateFolder, | ||
| 445 | "-o", msiPath | ||
| 446 | }); | ||
| 447 | |||
| 448 | result.AssertSuccess(); | ||
| 449 | |||
| 450 | Assert.True(File.Exists(msiPath)); | ||
| 451 | var results = Query.QueryDatabase(msiPath, new[] { "Environment" }); | ||
| 452 | WixAssert.CompareLineByLine(new[] | ||
| 453 | { | ||
| 454 | "Environment:PATH\t=-*PATH\t[INSTALLFOLDER]; ;[~]\tWixEnvironmentTest", | ||
| 455 | "Environment:WixEnvironmentTest1\t=-WixEnvTest1\t\tWixEnvironmentTest", | ||
| 456 | "Environment:WixEnvironmentTest2\t+-WixEnvTest1\t\tWixEnvironmentTest", | ||
| 457 | "Environment:WixEnvironmentTest3\t!-WixEnvTest1\t\tWixEnvironmentTest", | ||
| 458 | "Environment:WixEnvironmentTest4\t=-*WIX\t[INSTALLFOLDER]\tWixEnvironmentTest", | ||
| 459 | }, results); | ||
| 460 | } | ||
| 461 | } | ||
| 462 | |||
| 463 | [Fact(Skip = "Test demonstrates failure")] | ||
| 464 | public void PopulatesExampleTableBecauseOfEnsureTable() | ||
| 465 | { | ||
| 466 | var folder = TestData.Get(@"TestData"); | ||
| 467 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 468 | |||
| 469 | using (var fs = new DisposableFileSystem()) | ||
| 470 | { | ||
| 471 | var baseFolder = fs.GetFolder(); | ||
| 472 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 473 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 474 | |||
| 475 | var result = WixRunner.Execute(new[] | ||
| 476 | { | ||
| 477 | "build", | ||
| 478 | Path.Combine(folder, "EnsureTable", "EnsureTable.wxs"), | ||
| 479 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 480 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 481 | "-ext", extensionPath, | ||
| 482 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 483 | "-intermediateFolder", intermediateFolder, | ||
| 484 | "-o", msiPath | ||
| 485 | }); | ||
| 486 | |||
| 487 | result.AssertSuccess(); | ||
| 488 | |||
| 489 | Assert.True(File.Exists(msiPath)); | ||
| 490 | var results = Query.QueryDatabaseByTable(msiPath, new[] { "Wix4Example" }); | ||
| 491 | Assert.Empty(results["Wix4Example"]); | ||
| 492 | } | ||
| 493 | } | ||
| 494 | |||
| 495 | [Fact] | ||
| 496 | public void PopulatesFeatureTableWithParent() | ||
| 497 | { | ||
| 498 | var folder = TestData.Get(@"TestData"); | ||
| 499 | |||
| 500 | using (var fs = new DisposableFileSystem()) | ||
| 501 | { | ||
| 502 | var baseFolder = fs.GetFolder(); | ||
| 503 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 504 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 505 | |||
| 506 | var result = WixRunner.Execute(new[] | ||
| 507 | { | ||
| 508 | "build", | ||
| 509 | Path.Combine(folder, "FeatureGroup", "FeatureGroup.wxs"), | ||
| 510 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 511 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 512 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 513 | "-intermediateFolder", intermediateFolder, | ||
| 514 | "-o", msiPath | ||
| 515 | }); | ||
| 516 | |||
| 517 | result.AssertSuccess(); | ||
| 518 | |||
| 519 | Assert.True(File.Exists(msiPath)); | ||
| 520 | var results = Query.QueryDatabase(msiPath, new[] { "Feature" }); | ||
| 521 | WixAssert.CompareLineByLine(new[] | ||
| 522 | { | ||
| 523 | "Feature:ChildFeature\tParentFeature\tChildFeatureTitle\t\t2\t1\t\t0", | ||
| 524 | "Feature:ParentFeature\t\tParentFeatureTitle\t\t2\t1\t\t0", | ||
| 525 | "Feature:ProductFeature\t\tMsiPackageTitle\t\t2\t1\t\t0", | ||
| 526 | }, results); | ||
| 527 | } | ||
| 528 | } | ||
| 529 | |||
| 530 | [Fact] | ||
| 531 | public void PopulatesFontTableFromFontTitle() | ||
| 532 | { | ||
| 533 | var folder = TestData.Get(@"TestData"); | ||
| 534 | |||
| 535 | using (var fs = new DisposableFileSystem()) | ||
| 536 | { | ||
| 537 | var baseFolder = fs.GetFolder(); | ||
| 538 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 539 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 540 | |||
| 541 | var result = WixRunner.Execute(new[] | ||
| 542 | { | ||
| 543 | "build", | ||
| 544 | Path.Combine(folder, "Font", "FontTitle.wxs"), | ||
| 545 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 546 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 547 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 548 | "-intermediateFolder", intermediateFolder, | ||
| 549 | "-o", msiPath | ||
| 550 | }); | ||
| 551 | |||
| 552 | result.AssertSuccess(); | ||
| 553 | |||
| 554 | Assert.True(File.Exists(msiPath)); | ||
| 555 | var results = Query.QueryDatabase(msiPath, new[] { "Font" }); | ||
| 556 | WixAssert.CompareLineByLine(new[] | ||
| 557 | { | ||
| 558 | "Font:test.txt\tFakeFont", | ||
| 559 | }, results); | ||
| 560 | } | ||
| 561 | } | ||
| 562 | |||
| 563 | [Fact] | ||
| 564 | public void PopulatesFontTableFromTrueType() | ||
| 565 | { | ||
| 566 | var folder = TestData.Get(@"TestData"); | ||
| 567 | |||
| 568 | using (var fs = new DisposableFileSystem()) | ||
| 569 | { | ||
| 570 | var baseFolder = fs.GetFolder(); | ||
| 571 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 572 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 573 | |||
| 574 | var result = WixRunner.Execute(new[] | ||
| 575 | { | ||
| 576 | "build", | ||
| 577 | Path.Combine(folder, "Font", "TrueType.wxs"), | ||
| 578 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 579 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 580 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 581 | "-intermediateFolder", intermediateFolder, | ||
| 582 | "-o", msiPath | ||
| 583 | }); | ||
| 584 | |||
| 585 | result.AssertSuccess(); | ||
| 586 | |||
| 587 | Assert.True(File.Exists(msiPath)); | ||
| 588 | var results = Query.QueryDatabase(msiPath, new[] { "Font" }); | ||
| 589 | WixAssert.CompareLineByLine(new[] | ||
| 590 | { | ||
| 591 | "Font:TrueTypeFontFile\t", | ||
| 592 | }, results); | ||
| 593 | } | ||
| 594 | } | ||
| 595 | |||
| 596 | [Fact] | ||
| 597 | public void PopulatesInstallExecuteSequenceTable() | ||
| 598 | { | ||
| 599 | var folder = TestData.Get(@"TestData"); | ||
| 600 | |||
| 601 | using (var fs = new DisposableFileSystem()) | ||
| 602 | { | ||
| 603 | var baseFolder = fs.GetFolder(); | ||
| 604 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 605 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 606 | |||
| 607 | var result = WixRunner.Execute(new[] | ||
| 608 | { | ||
| 609 | "build", | ||
| 610 | Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), | ||
| 611 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 612 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 613 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 614 | "-intermediateFolder", intermediateFolder, | ||
| 615 | "-o", msiPath | ||
| 616 | }); | ||
| 617 | |||
| 618 | result.AssertSuccess(); | ||
| 619 | |||
| 620 | Assert.True(File.Exists(msiPath)); | ||
| 621 | var results = Query.QueryDatabase(msiPath, new[] { "InstallExecuteSequence" }); | ||
| 622 | WixAssert.CompareLineByLine(new[] | ||
| 623 | { | ||
| 624 | "InstallExecuteSequence:CostFinalize\t\t1000", | ||
| 625 | "InstallExecuteSequence:CostInitialize\t\t800", | ||
| 626 | "InstallExecuteSequence:CreateFolders\t\t3700", | ||
| 627 | "InstallExecuteSequence:FileCost\t\t900", | ||
| 628 | "InstallExecuteSequence:FindRelatedProducts\t\t25", | ||
| 629 | "InstallExecuteSequence:InstallFiles\t\t4000", | ||
| 630 | "InstallExecuteSequence:InstallFinalize\t\t6600", | ||
| 631 | "InstallExecuteSequence:InstallInitialize\t\t1500", | ||
| 632 | "InstallExecuteSequence:InstallValidate\t\t1400", | ||
| 633 | "InstallExecuteSequence:LaunchConditions\t\t100", | ||
| 634 | "InstallExecuteSequence:MigrateFeatureStates\t\t1200", | ||
| 635 | "InstallExecuteSequence:ProcessComponents\t\t1600", | ||
| 636 | "InstallExecuteSequence:PublishFeatures\t\t6300", | ||
| 637 | "InstallExecuteSequence:PublishProduct\t\t6400", | ||
| 638 | "InstallExecuteSequence:RegisterProduct\t\t6100", | ||
| 639 | "InstallExecuteSequence:RegisterUser\t\t6000", | ||
| 640 | "InstallExecuteSequence:RemoveExistingProducts\t\t1401", | ||
| 641 | "InstallExecuteSequence:RemoveFiles\t\t3500", | ||
| 642 | "InstallExecuteSequence:RemoveFolders\t\t3600", | ||
| 643 | "InstallExecuteSequence:UnpublishFeatures\t\t1800", | ||
| 644 | "InstallExecuteSequence:ValidateProductID\t\t700", | ||
| 645 | }, results); | ||
| 646 | } | ||
| 647 | } | ||
| 648 | |||
| 649 | [Fact] | ||
| 650 | public void PopulatesLockPermissionsTableWithEmptyPermissions() | ||
| 651 | { | ||
| 652 | var folder = TestData.Get(@"TestData"); | ||
| 653 | |||
| 654 | using (var fs = new DisposableFileSystem()) | ||
| 655 | { | ||
| 656 | var baseFolder = fs.GetFolder(); | ||
| 657 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 658 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 659 | |||
| 660 | var result = WixRunner.Execute(new[] | ||
| 661 | { | ||
| 662 | "build", | ||
| 663 | Path.Combine(folder, "LockPermissions", "EmptyPermissions.wxs"), | ||
| 664 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 665 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 666 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 667 | "-intermediateFolder", intermediateFolder, | ||
| 668 | "-o", msiPath | ||
| 669 | }); | ||
| 670 | |||
| 671 | result.AssertSuccess(); | ||
| 672 | |||
| 673 | Assert.True(File.Exists(msiPath)); | ||
| 674 | var results = Query.QueryDatabase(msiPath, new[] { "LockPermissions" }); | ||
| 675 | WixAssert.CompareLineByLine(new[] | ||
| 676 | { | ||
| 677 | "LockPermissions:INSTALLFOLDER\tCreateFolder\t\tAdministrator\t0", | ||
| 678 | }, results); | ||
| 679 | } | ||
| 680 | } | ||
| 681 | |||
| 682 | [Fact] | ||
| 683 | public void PopulatesMsiAssemblyTables() | ||
| 684 | { | ||
| 685 | var folder = TestData.Get(@"TestData"); | ||
| 686 | |||
| 687 | using (var fs = new DisposableFileSystem()) | ||
| 688 | { | ||
| 689 | var baseFolder = fs.GetFolder(); | ||
| 690 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 691 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 692 | |||
| 693 | var result = WixRunner.Execute(new[] | ||
| 694 | { | ||
| 695 | "build", | ||
| 696 | Path.Combine(folder, "Assembly", "Win32Assembly.wxs"), | ||
| 697 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 698 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 699 | "-bindpath", Path.Combine(folder, "Assembly", "data"), | ||
| 700 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 701 | "-intermediateFolder", intermediateFolder, | ||
| 702 | "-o", msiPath | ||
| 703 | }); | ||
| 704 | |||
| 705 | result.AssertSuccess(); | ||
| 706 | |||
| 707 | Assert.True(File.Exists(msiPath)); | ||
| 708 | var results = Query.QueryDatabase(msiPath, new[] { "MsiAssembly", "MsiAssemblyName" }); | ||
| 709 | WixAssert.CompareLineByLine(new[] | ||
| 710 | { | ||
| 711 | "MsiAssembly:test.txt\tProductFeature\ttest.dll.manifest\t\t1", | ||
| 712 | "MsiAssemblyName:test.txt\tname\tMyApplication.app", | ||
| 713 | "MsiAssemblyName:test.txt\tversion\t1.0.0.0", | ||
| 714 | }, results); | ||
| 715 | } | ||
| 716 | } | ||
| 717 | |||
| 718 | [Fact] | ||
| 719 | public void PopulatesReserveCostTable() | ||
| 720 | { | ||
| 721 | var folder = TestData.Get(@"TestData"); | ||
| 722 | |||
| 723 | using (var fs = new DisposableFileSystem()) | ||
| 724 | { | ||
| 725 | var baseFolder = fs.GetFolder(); | ||
| 726 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 727 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 728 | |||
| 729 | var result = WixRunner.Execute(new[] | ||
| 730 | { | ||
| 731 | "build", | ||
| 732 | Path.Combine(folder, "ReserveCost", "ReserveCost.wxs"), | ||
| 733 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 734 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 735 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 736 | "-intermediateFolder", intermediateFolder, | ||
| 737 | "-o", msiPath | ||
| 738 | }); | ||
| 739 | |||
| 740 | result.AssertSuccess(); | ||
| 741 | |||
| 742 | Assert.True(File.Exists(msiPath)); | ||
| 743 | var results = Query.QueryDatabase(msiPath, new[] { "ReserveCost" }); | ||
| 744 | WixAssert.CompareLineByLine(new[] | ||
| 745 | { | ||
| 746 | "ReserveCost:TestCost\tReserveCostComp\tINSTALLFOLDER\t100\t200", | ||
| 747 | }, results); | ||
| 748 | } | ||
| 749 | } | ||
| 750 | |||
| 751 | [Fact] | ||
| 752 | public void PopulatesServiceTables() | ||
| 753 | { | ||
| 754 | var folder = TestData.Get(@"TestData"); | ||
| 755 | |||
| 756 | using (var fs = new DisposableFileSystem()) | ||
| 757 | { | ||
| 758 | var baseFolder = fs.GetFolder(); | ||
| 759 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 760 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 761 | |||
| 762 | var result = WixRunner.Execute(new[] | ||
| 763 | { | ||
| 764 | "build", | ||
| 765 | Path.Combine(folder, "ServiceInstall", "OwnProcess.wxs"), | ||
| 766 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 767 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 768 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 769 | "-intermediateFolder", intermediateFolder, | ||
| 770 | "-o", msiPath | ||
| 771 | }); | ||
| 772 | |||
| 773 | result.AssertSuccess(); | ||
| 774 | |||
| 775 | Assert.True(File.Exists(msiPath)); | ||
| 776 | var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall", "ServiceControl" }); | ||
| 777 | WixAssert.CompareLineByLine(new[] | ||
| 778 | { | ||
| 779 | "ServiceControl:SampleService\tSampleService\t161\t\t1\ttest.txt", | ||
| 780 | "ServiceInstall:SampleService\tSampleService\t\t16\t4\t0\t\t\t\t\t\ttest.txt\t", | ||
| 781 | }, results); | ||
| 782 | } | ||
| 783 | } | ||
| 784 | |||
| 785 | [Fact] | ||
| 786 | public void PopulatesTextStyleTableWhenColorIsNull() | ||
| 787 | { | ||
| 788 | var folder = TestData.Get(@"TestData"); | ||
| 789 | |||
| 790 | using (var fs = new DisposableFileSystem()) | ||
| 791 | { | ||
| 792 | var baseFolder = fs.GetFolder(); | ||
| 793 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 794 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 795 | |||
| 796 | var result = WixRunner.Execute(new[] | ||
| 797 | { | ||
| 798 | "build", | ||
| 799 | Path.Combine(folder, "TextStyle", "ColorNull.wxs"), | ||
| 800 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 801 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 802 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 803 | "-intermediateFolder", intermediateFolder, | ||
| 804 | "-o", msiPath | ||
| 805 | }); | ||
| 806 | |||
| 807 | result.AssertSuccess(); | ||
| 808 | |||
| 809 | Assert.True(File.Exists(msiPath)); | ||
| 810 | var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); | ||
| 811 | WixAssert.CompareLineByLine(new[] | ||
| 812 | { | ||
| 813 | "TextStyle:FirstTextStyle\tArial\t2\t\t", | ||
| 814 | }, results); | ||
| 815 | } | ||
| 816 | } | ||
| 817 | |||
| 818 | [Fact] | ||
| 819 | public void PopulatesTextStyleTableWhenSizeIsLocalized() | ||
| 820 | { | ||
| 821 | var folder = TestData.Get(@"TestData"); | ||
| 822 | |||
| 823 | using (var fs = new DisposableFileSystem()) | ||
| 824 | { | ||
| 825 | var baseFolder = fs.GetFolder(); | ||
| 826 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 827 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 828 | |||
| 829 | var result = WixRunner.Execute(new[] | ||
| 830 | { | ||
| 831 | "build", | ||
| 832 | Path.Combine(folder, "TextStyle", "SizeLocalized.wxs"), | ||
| 833 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 834 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 835 | "-loc", Path.Combine(folder, "TextStyle", "SizeLocalized.en-us.wxl"), | ||
| 836 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 837 | "-intermediateFolder", intermediateFolder, | ||
| 838 | "-o", msiPath, | ||
| 839 | }); | ||
| 840 | |||
| 841 | result.AssertSuccess(); | ||
| 842 | |||
| 843 | Assert.True(File.Exists(msiPath)); | ||
| 844 | var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); | ||
| 845 | WixAssert.CompareLineByLine(new[] | ||
| 846 | { | ||
| 847 | "TextStyle:CustomFont\tTahoma\t8\t\t", | ||
| 848 | }, results); | ||
| 849 | } | ||
| 850 | } | ||
| 851 | |||
| 852 | [Fact] | ||
| 853 | public void PopulatesTypeLibTableWhenLanguageIsZero() | ||
| 854 | { | ||
| 855 | var folder = TestData.Get(@"TestData"); | ||
| 856 | |||
| 857 | using (var fs = new DisposableFileSystem()) | ||
| 858 | { | ||
| 859 | var baseFolder = fs.GetFolder(); | ||
| 860 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 861 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 862 | |||
| 863 | var result = WixRunner.Execute(new[] | ||
| 864 | { | ||
| 865 | "build", | ||
| 866 | Path.Combine(folder, "TypeLib", "Language0.wxs"), | ||
| 867 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 868 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 869 | "-intermediateFolder", intermediateFolder, | ||
| 870 | "-o", msiPath | ||
| 871 | }); | ||
| 872 | |||
| 873 | result.AssertSuccess(); | ||
| 874 | |||
| 875 | Assert.True(File.Exists(msiPath)); | ||
| 876 | var results = Query.QueryDatabase(msiPath, new[] { "TypeLib" }); | ||
| 877 | WixAssert.CompareLineByLine(new[] | ||
| 878 | { | ||
| 879 | "TypeLib:{765BE8EE-BD7F-491E-90D2-C5A972462B50}\t0\tTypeLibComp\t\t\t\tProductFeature\t", | ||
| 880 | }, results); | ||
| 881 | } | ||
| 882 | } | ||
| 883 | |||
| 884 | [Fact] | ||
| 885 | public void PopulatesUpgradeTableFromManualUpgrade() | ||
| 886 | { | ||
| 887 | var folder = TestData.Get(@"TestData\ManualUpgrade"); | ||
| 888 | |||
| 889 | using (var fs = new DisposableFileSystem()) | ||
| 890 | { | ||
| 891 | var intermediateFolder = fs.GetFolder(); | ||
| 892 | var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); | ||
| 893 | |||
| 894 | var result = WixRunner.Execute(new[] | ||
| 895 | { | ||
| 896 | "build", | ||
| 897 | Path.Combine(folder, "Package.wxs"), | ||
| 898 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 899 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 900 | "-bindpath", Path.Combine(folder, "data"), | ||
| 901 | "-intermediateFolder", intermediateFolder, | ||
| 902 | "-o", msiPath | ||
| 903 | }, out var messages); | ||
| 904 | |||
| 905 | Assert.Equal(0, result); | ||
| 906 | |||
| 907 | Assert.True(File.Exists(msiPath)); | ||
| 908 | var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); | ||
| 909 | WixAssert.CompareLineByLine(new[] | ||
| 910 | { | ||
| 911 | "Upgrade:{01120000-00E0-0000-0000-0000000FF1CE}\t12.0.0\t13.0.0\t\t260\t\tBLAHBLAHBLAH", | ||
| 912 | }, results); | ||
| 913 | } | ||
| 914 | } | ||
| 915 | |||
| 916 | [Fact] | ||
| 917 | public void PopulatesUpgradeTableFromDetectOnlyUpgrade() | ||
| 918 | { | ||
| 919 | var folder = TestData.Get(@"TestData"); | ||
| 920 | |||
| 921 | using (var fs = new DisposableFileSystem()) | ||
| 922 | { | ||
| 923 | var baseFolder = fs.GetFolder(); | ||
| 924 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 925 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 926 | |||
| 927 | var result = WixRunner.Execute(new[] | ||
| 928 | { | ||
| 929 | "build", | ||
| 930 | Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), | ||
| 931 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 932 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 933 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 934 | "-intermediateFolder", intermediateFolder, | ||
| 935 | "-o", msiPath | ||
| 936 | }); | ||
| 937 | |||
| 938 | result.AssertSuccess(); | ||
| 939 | |||
| 940 | Assert.True(File.Exists(msiPath)); | ||
| 941 | var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); | ||
| 942 | WixAssert.CompareLineByLine(new[] | ||
| 943 | { | ||
| 944 | "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", | ||
| 945 | "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", | ||
| 946 | "Upgrade:{B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D}\t1.0.0\t\t\t256\t\tRELPRODFOUND", | ||
| 947 | }, results); | ||
| 948 | |||
| 949 | var prefix = "Property:SecureCustomProperties\t"; | ||
| 950 | var secureProperties = Query.QueryDatabase(msiPath, new[] { "Property" }).Where(p => p.StartsWith(prefix)).Single(); | ||
| 951 | WixAssert.CompareLineByLine(new[] | ||
| 952 | { | ||
| 953 | "RELPRODFOUND", | ||
| 954 | "WIX_DOWNGRADE_DETECTED", | ||
| 955 | "WIX_UPGRADE_DETECTED", | ||
| 956 | }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p).ToArray()); | ||
| 957 | } | ||
| 958 | } | ||
| 959 | |||
| 960 | [Fact] | ||
| 961 | public void CanMergeModule() | ||
| 962 | { | ||
| 963 | var folder = TestData.Get(@"TestData\SimpleMerge"); | ||
| 964 | |||
| 965 | using (var fs = new DisposableFileSystem()) | ||
| 966 | { | ||
| 967 | var intermediateFolder = fs.GetFolder(); | ||
| 968 | var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); | ||
| 969 | var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab"); | ||
| 970 | |||
| 971 | var result = WixRunner.Execute(new[] | ||
| 972 | { | ||
| 973 | "build", | ||
| 974 | Path.Combine(folder, "Package.wxs"), | ||
| 975 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 976 | "-bindpath", Path.Combine(folder, ".data"), | ||
| 977 | "-intermediateFolder", intermediateFolder, | ||
| 978 | "-o", msiPath | ||
| 979 | }); | ||
| 980 | |||
| 981 | result.AssertSuccess(); | ||
| 982 | |||
| 983 | Assert.True(File.Exists(msiPath)); | ||
| 984 | Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); | ||
| 985 | |||
| 986 | var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); | ||
| 987 | var section = intermediate.Sections.Single(); | ||
| 988 | Assert.Empty(section.Symbols.OfType<FileSymbol>()); | ||
| 989 | |||
| 990 | var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); | ||
| 991 | Assert.Empty(data.Tables["File"].Rows); | ||
| 992 | |||
| 993 | var results = Query.QueryDatabase(msiPath, new[] { "File" }); | ||
| 994 | WixAssert.CompareLineByLine(new[] | ||
| 995 | { | ||
| 996 | "File:filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent.243FB739_4D05_472F_9CFB_EF6B1017B6DE\ttest.txt\t17\t\t\t512\t0" | ||
| 997 | }, results); | ||
| 998 | |||
| 999 | var files = Query.GetCabinetFiles(cabPath); | ||
| 1000 | WixAssert.CompareLineByLine(new[] | ||
| 1001 | { | ||
| 1002 | "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" | ||
| 1003 | }, files.Select(f => f.Name).ToArray()); | ||
| 1004 | } | ||
| 1005 | } | ||
| 1006 | |||
| 1007 | [Fact] | ||
| 1008 | public void CanPublishComponentWithMultipleFeatureComponents() | ||
| 1009 | { | ||
| 1010 | var folder = TestData.Get(@"TestData\PublishComponent"); | ||
| 1011 | |||
| 1012 | using (var fs = new DisposableFileSystem()) | ||
| 1013 | { | ||
| 1014 | var baseFolder = fs.GetFolder(); | ||
| 1015 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 1016 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 1017 | |||
| 1018 | var result = WixRunner.Execute(new[] | ||
| 1019 | { | ||
| 1020 | "build", | ||
| 1021 | Path.Combine(folder, "Package.wxs"), | ||
| 1022 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 1023 | "-bindpath", Path.Combine(folder, "data"), | ||
| 1024 | "-intermediateFolder", intermediateFolder, | ||
| 1025 | "-o", msiPath | ||
| 1026 | }); | ||
| 1027 | |||
| 1028 | result.AssertSuccess(); | ||
| 1029 | |||
| 1030 | Assert.True(File.Exists(msiPath)); | ||
| 1031 | var results = Query.QueryDatabase(msiPath, new[] { "PublishComponent" }); | ||
| 1032 | WixAssert.CompareLineByLine(new[] | ||
| 1033 | { | ||
| 1034 | "PublishComponent:{0A82C8F6-9CE9-4336-B8BE-91A39B5F7081} Qualifier2 Component2 AppData2 ProductFeature2", | ||
| 1035 | "PublishComponent:{BD245B5A-EC33-46ED-98FF-E9D3D416AD04} Qualifier1 Component1 AppData1 ProductFeature1", | ||
| 1036 | }, results); | ||
| 1037 | } | ||
| 1038 | } | ||
| 1039 | } | ||
| 1040 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs new file mode 100644 index 00000000..a566b490 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixBuildTools.TestSupport; | ||
| 7 | using WixToolset.Core.TestPackage; | ||
| 8 | using Xunit; | ||
| 9 | |||
| 10 | public class MsiTransactionFixture | ||
| 11 | { | ||
| 12 | [Fact] | ||
| 13 | public void CantBuildX64AfterX86Bundle() | ||
| 14 | { | ||
| 15 | var folder = TestData.Get(@"TestData"); | ||
| 16 | |||
| 17 | using (var fs = new DisposableFileSystem()) | ||
| 18 | { | ||
| 19 | var baseFolder = fs.GetFolder(); | ||
| 20 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 21 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 22 | var exePath = Path.Combine(binFolder, "test.exe"); | ||
| 23 | |||
| 24 | BuildMsiPackages(folder, intermediateFolder, binFolder); | ||
| 25 | |||
| 26 | var result = WixRunner.Execute(new[] | ||
| 27 | { | ||
| 28 | "build", | ||
| 29 | "-sw1151", // this is expected for this test | ||
| 30 | Path.Combine(folder, "MsiTransaction", "X64AfterX86Bundle.wxs"), | ||
| 31 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 32 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 33 | "-bindpath", binFolder, | ||
| 34 | "-intermediateFolder", intermediateFolder, | ||
| 35 | "-o", exePath, | ||
| 36 | }); | ||
| 37 | |||
| 38 | Assert.Equal(390, result.ExitCode); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | [Fact] | ||
| 43 | public void CanBuildX86AfterX64Bundle() | ||
| 44 | { | ||
| 45 | var folder = TestData.Get(@"TestData"); | ||
| 46 | |||
| 47 | using (var fs = new DisposableFileSystem()) | ||
| 48 | { | ||
| 49 | var baseFolder = fs.GetFolder(); | ||
| 50 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 51 | var binFolder = Path.Combine(baseFolder, "bin"); | ||
| 52 | var exePath = Path.Combine(binFolder, "test.exe"); | ||
| 53 | |||
| 54 | BuildMsiPackages(folder, intermediateFolder, binFolder); | ||
| 55 | |||
| 56 | var result = WixRunner.Execute(new[] | ||
| 57 | { | ||
| 58 | "build", | ||
| 59 | "-sw1151", // this is expected for this test | ||
| 60 | Path.Combine(folder, "MsiTransaction", "X86AfterX64Bundle.wxs"), | ||
| 61 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 62 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 63 | "-bindpath", binFolder, | ||
| 64 | "-intermediateFolder", intermediateFolder, | ||
| 65 | "-o", exePath, | ||
| 66 | }); | ||
| 67 | |||
| 68 | result.AssertSuccess(); | ||
| 69 | |||
| 70 | Assert.True(File.Exists(exePath)); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | private static void BuildMsiPackages(string folder, string intermediateFolder, string binFolder) | ||
| 75 | { | ||
| 76 | var result = WixRunner.Execute(new[] | ||
| 77 | { | ||
| 78 | "build", | ||
| 79 | Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), | ||
| 80 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 81 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 82 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 83 | "-intermediateFolder", intermediateFolder, | ||
| 84 | "-o", Path.Combine(binFolder, "FirstX86", "FirstX86.msi"), | ||
| 85 | }); | ||
| 86 | |||
| 87 | result.AssertSuccess(); | ||
| 88 | |||
| 89 | result = WixRunner.Execute(new[] | ||
| 90 | { | ||
| 91 | "build", | ||
| 92 | Path.Combine(folder, "MsiTransaction", "SecondX86.wxs"), | ||
| 93 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 94 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 95 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 96 | "-intermediateFolder", intermediateFolder, | ||
| 97 | "-o", Path.Combine(binFolder, "SecondX86", "SecondX86.msi"), | ||
| 98 | }); | ||
| 99 | |||
| 100 | result.AssertSuccess(); | ||
| 101 | |||
| 102 | result = WixRunner.Execute(new[] | ||
| 103 | { | ||
| 104 | "build", | ||
| 105 | Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), | ||
| 106 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 107 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 108 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 109 | "-intermediateFolder", intermediateFolder, | ||
| 110 | "-arch", "x64", | ||
| 111 | "-o", Path.Combine(binFolder, "FirstX64", "FirstX64.msi"), | ||
| 112 | }); | ||
| 113 | |||
| 114 | result.AssertSuccess(); | ||
| 115 | |||
| 116 | result = WixRunner.Execute(new[] | ||
| 117 | { | ||
| 118 | "build", | ||
| 119 | Path.Combine(folder, "MsiTransaction", "SecondX64.wxs"), | ||
| 120 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 121 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 122 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 123 | "-intermediateFolder", intermediateFolder, | ||
| 124 | "-arch", "x64", | ||
| 125 | "-o", Path.Combine(binFolder, "SecondX64", "SecondX64.msi"), | ||
| 126 | }); | ||
| 127 | |||
| 128 | result.AssertSuccess(); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs new file mode 100644 index 00000000..475afcf0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixBuildTools.TestSupport; | ||
| 7 | using WixToolset.Core.TestPackage; | ||
| 8 | using Xunit; | ||
| 9 | |||
| 10 | public class MsuPackageFixture | ||
| 11 | { | ||
| 12 | [Fact] | ||
| 13 | public void CanBuildBundleWithMsuPackage() | ||
| 14 | { | ||
| 15 | var folder = TestData.Get(@"TestData", "MsuPackage"); | ||
| 16 | |||
| 17 | using (var fs = new DisposableFileSystem()) | ||
| 18 | { | ||
| 19 | var baseFolder = fs.GetFolder(); | ||
| 20 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 21 | |||
| 22 | var result = WixRunner.Execute(new[] | ||
| 23 | { | ||
| 24 | "build", | ||
| 25 | Path.Combine(folder, "Bundle.wxs"), | ||
| 26 | "-bindpath", Path.Combine(folder, "data"), | ||
| 27 | "-intermediateFolder", intermediateFolder, | ||
| 28 | "-o", Path.Combine(baseFolder, "bin", "test.exe") | ||
| 29 | }); | ||
| 30 | |||
| 31 | result.AssertSuccess(); | ||
| 32 | Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.exe"))); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | } | ||
| 36 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs new file mode 100644 index 00000000..6b2d8bfa --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs | |||
| @@ -0,0 +1,211 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using WixBuildTools.TestSupport; | ||
| 9 | using WixToolset.Core.TestPackage; | ||
| 10 | using Xunit; | ||
| 11 | |||
| 12 | public class PackagePayloadFixture | ||
| 13 | { | ||
| 14 | [Fact] | ||
| 15 | public void CanSpecifyPackagePayloadInPayloadGroup() | ||
| 16 | { | ||
| 17 | var folder = TestData.Get(@"TestData"); | ||
| 18 | |||
| 19 | using (var fs = new DisposableFileSystem()) | ||
| 20 | { | ||
| 21 | var baseFolder = fs.GetFolder(); | ||
| 22 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 23 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 24 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 25 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 26 | |||
| 27 | var result = WixRunner.Execute(new[] | ||
| 28 | { | ||
| 29 | "build", | ||
| 30 | Path.Combine(folder, "PackagePayload", "PackagePayloadInPayloadGroup.wxs"), | ||
| 31 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 32 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 33 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 34 | "-intermediateFolder", intermediateFolder, | ||
| 35 | "-o", bundlePath, | ||
| 36 | }); | ||
| 37 | |||
| 38 | result.AssertSuccess(); | ||
| 39 | |||
| 40 | Assert.True(File.Exists(bundlePath)); | ||
| 41 | |||
| 42 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 43 | extractResult.AssertSuccess(); | ||
| 44 | |||
| 45 | var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); | ||
| 46 | var ignoreAttributesByElementName = new Dictionary<string, List<string>> | ||
| 47 | { | ||
| 48 | { "ExePackage", new List<string> { "CacheId", "InstallSize", "Size" } }, | ||
| 49 | }; | ||
| 50 | Assert.Equal(1, exePackageElements.Count); | ||
| 51 | Assert.Equal("<ExePackage Id='PackagePayloadInPayloadGroup' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='yes' Vital='yes' RollbackBoundaryForward='WixDefaultBoundary' RollbackBoundaryBackward='WixDefaultBoundary' LogPathVariable='WixBundleLog_PackagePayloadInPayloadGroup' RollbackLogPathVariable='WixBundleRollbackLog_PackagePayloadInPayloadGroup' DetectCondition='none' InstallArguments='' UninstallArguments='' RepairArguments='' Repairable='no'><PayloadRef Id='burn.exe' /></ExePackage>", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); | ||
| 52 | |||
| 53 | var payloadElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='burn.exe']"); | ||
| 54 | Assert.Equal(1, payloadElements.Count); | ||
| 55 | Assert.Equal("<Payload Id='burn.exe' FilePath='burn.exe' FileSize='463360' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Packaging='embedded' SourcePath='a0' Container='WixAttachedContainer' />", payloadElements[0].GetTestXml()); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | [Fact] | ||
| 60 | public void ErrorWhenMissingSourceFileAndHash() | ||
| 61 | { | ||
| 62 | var folder = TestData.Get(@"TestData", "PackagePayload"); | ||
| 63 | |||
| 64 | using (var fs = new DisposableFileSystem()) | ||
| 65 | { | ||
| 66 | var baseFolder = fs.GetFolder(); | ||
| 67 | |||
| 68 | var result = WixRunner.Execute(false, new[] | ||
| 69 | { | ||
| 70 | "build", | ||
| 71 | Path.Combine(folder, "MissingSourceFileAndHash.wxs"), | ||
| 72 | "-o", Path.Combine(baseFolder, "test.wixlib") | ||
| 73 | }); | ||
| 74 | |||
| 75 | Assert.Equal(44, result.ExitCode); | ||
| 76 | WixAssert.CompareLineByLine(new[] | ||
| 77 | { | ||
| 78 | "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.", | ||
| 79 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | [Fact] | ||
| 84 | public void ErrorWhenMissingSourceFileAndName() | ||
| 85 | { | ||
| 86 | var folder = TestData.Get(@"TestData", "PackagePayload"); | ||
| 87 | |||
| 88 | using (var fs = new DisposableFileSystem()) | ||
| 89 | { | ||
| 90 | var baseFolder = fs.GetFolder(); | ||
| 91 | |||
| 92 | var result = WixRunner.Execute(false, new[] | ||
| 93 | { | ||
| 94 | "build", | ||
| 95 | Path.Combine(folder, "MissingSourceFileAndName.wxs"), | ||
| 96 | "-o", Path.Combine(baseFolder, "test.wixlib") | ||
| 97 | }); | ||
| 98 | |||
| 99 | Assert.Equal(44, result.ExitCode); | ||
| 100 | WixAssert.CompareLineByLine(new[] | ||
| 101 | { | ||
| 102 | "The MsiPackagePayload element's Name or SourceFile attribute was not found; one of these is required.", | ||
| 103 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | [Fact] | ||
| 108 | public void ErrorWhenSpecifiedHash() | ||
| 109 | { | ||
| 110 | var folder = TestData.Get(@"TestData", "PackagePayload"); | ||
| 111 | |||
| 112 | using (var fs = new DisposableFileSystem()) | ||
| 113 | { | ||
| 114 | var baseFolder = fs.GetFolder(); | ||
| 115 | |||
| 116 | var result = WixRunner.Execute(new[] | ||
| 117 | { | ||
| 118 | "build", | ||
| 119 | Path.Combine(folder, "SpecifiedHash.wxs"), | ||
| 120 | "-o", Path.Combine(baseFolder, "test.wixlib") | ||
| 121 | }); | ||
| 122 | |||
| 123 | Assert.Equal(4, result.ExitCode); | ||
| 124 | WixAssert.CompareLineByLine(new[] | ||
| 125 | { | ||
| 126 | "The MspPackagePayload element contains an unexpected attribute 'Hash'.", | ||
| 127 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | [Fact] | ||
| 132 | public void ErrorWhenSpecifiedHashAndMissingDownloadUrl() | ||
| 133 | { | ||
| 134 | var folder = TestData.Get(@"TestData", "PackagePayload"); | ||
| 135 | |||
| 136 | using (var fs = new DisposableFileSystem()) | ||
| 137 | { | ||
| 138 | var baseFolder = fs.GetFolder(); | ||
| 139 | |||
| 140 | var result = WixRunner.Execute(new[] | ||
| 141 | { | ||
| 142 | "build", | ||
| 143 | Path.Combine(folder, "SpecifiedHashAndMissingDownloadUrl.wxs"), | ||
| 144 | "-o", Path.Combine(baseFolder, "test.wixlib") | ||
| 145 | }); | ||
| 146 | |||
| 147 | Assert.Equal(10, result.ExitCode); | ||
| 148 | WixAssert.CompareLineByLine(new[] | ||
| 149 | { | ||
| 150 | "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.", | ||
| 151 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | [Fact] | ||
| 156 | public void ErrorWhenSpecifiedSourceFileAndHash() | ||
| 157 | { | ||
| 158 | var folder = TestData.Get(@"TestData", "PackagePayload"); | ||
| 159 | |||
| 160 | using (var fs = new DisposableFileSystem()) | ||
| 161 | { | ||
| 162 | var baseFolder = fs.GetFolder(); | ||
| 163 | |||
| 164 | var result = WixRunner.Execute(new[] | ||
| 165 | { | ||
| 166 | "build", | ||
| 167 | Path.Combine(folder, "SpecifiedSourceFileAndHash.wxs"), | ||
| 168 | "-o", Path.Combine(baseFolder, "test.wixlib") | ||
| 169 | }); | ||
| 170 | |||
| 171 | Assert.Equal(35, result.ExitCode); | ||
| 172 | WixAssert.CompareLineByLine(new[] | ||
| 173 | { | ||
| 174 | "The ExePackagePayload/@Hash attribute cannot be specified when attribute SourceFile is present.", | ||
| 175 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | [Fact] | ||
| 180 | public void ErrorWhenWrongPackagePayloadInPayloadGroup() | ||
| 181 | { | ||
| 182 | var folder = TestData.Get(@"TestData"); | ||
| 183 | |||
| 184 | using (var fs = new DisposableFileSystem()) | ||
| 185 | { | ||
| 186 | var baseFolder = fs.GetFolder(); | ||
| 187 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 188 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 189 | |||
| 190 | var result = WixRunner.Execute(new[] | ||
| 191 | { | ||
| 192 | "build", | ||
| 193 | Path.Combine(folder, "PackagePayload", "WrongPackagePayloadInPayloadGroup.wxs"), | ||
| 194 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 195 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 196 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 197 | "-intermediateFolder", intermediateFolder, | ||
| 198 | "-o", bundlePath, | ||
| 199 | }); | ||
| 200 | |||
| 201 | Assert.Equal(407, result.ExitCode); | ||
| 202 | WixAssert.CompareLineByLine(new[] | ||
| 203 | { | ||
| 204 | "The ExePackagePayload element can only be used for ExePackages.", | ||
| 205 | "The location of the package related to previous error.", | ||
| 206 | "There is no payload defined for package 'WrongPackagePayloadInPayloadGroup'. This is specified on the MsiPackage element or a child MsiPackagePayload element.", | ||
| 207 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 208 | } | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs new file mode 100644 index 00000000..cdba85de --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.Linq; | ||
| 6 | using WixToolset.Core; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Symbols; | ||
| 9 | using WixToolset.Extensibility.Data; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | using Xunit; | ||
| 12 | |||
| 13 | public class ParseFixture | ||
| 14 | { | ||
| 15 | [Fact] | ||
| 16 | public void GeneratesCorrectCustomActionIdentifiers() | ||
| 17 | { | ||
| 18 | var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); | ||
| 19 | var section = new IntermediateSection("section", SectionType.Fragment); | ||
| 20 | var parseHelper = serviceProvider.GetService<IParseHelper>(); | ||
| 21 | |||
| 22 | parseHelper.CreateCustomActionReference(null, section, "CustomAction32", Platform.X86, CustomActionPlatforms.X86); | ||
| 23 | parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86); | ||
| 24 | parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); | ||
| 25 | parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86); | ||
| 26 | parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64); | ||
| 27 | |||
| 28 | var simpleReferences = section.Symbols.OfType<WixSimpleReferenceSymbol>(); | ||
| 29 | Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction32_X86").FirstOrDefault()); | ||
| 30 | Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_X86").FirstOrDefault()); | ||
| 31 | Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_A64").FirstOrDefault()); | ||
| 32 | Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X86").FirstOrDefault()); | ||
| 33 | Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X64").FirstOrDefault()); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs new file mode 100644 index 00000000..483e3fd5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs | |||
| @@ -0,0 +1,279 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.ComponentModel; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Runtime.InteropServices; | ||
| 11 | using System.Text; | ||
| 12 | using System.Xml; | ||
| 13 | using System.Xml.Linq; | ||
| 14 | using Example.Extension; | ||
| 15 | using WixBuildTools.TestSupport; | ||
| 16 | using WixToolset.Core.TestPackage; | ||
| 17 | using WixToolset.Data; | ||
| 18 | using WixToolset.Data.Burn; | ||
| 19 | using Xunit; | ||
| 20 | |||
| 21 | public class PatchFixture | ||
| 22 | { | ||
| 23 | private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd"; | ||
| 24 | |||
| 25 | [Fact] | ||
| 26 | public void CanBuildSimplePatch() | ||
| 27 | { | ||
| 28 | var folder = TestData.Get(@"TestData\PatchSingle"); | ||
| 29 | |||
| 30 | using (var fs = new DisposableFileSystem()) | ||
| 31 | { | ||
| 32 | var tempFolder = fs.GetFolder(); | ||
| 33 | |||
| 34 | var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); | ||
| 35 | var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); | ||
| 36 | var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); | ||
| 37 | var patchPath = Path.ChangeExtension(patchPdb, ".msp"); | ||
| 38 | |||
| 39 | Assert.True(File.Exists(baselinePdb)); | ||
| 40 | Assert.True(File.Exists(update1Pdb)); | ||
| 41 | |||
| 42 | var doc = GetExtractPatchXml(patchPath); | ||
| 43 | Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); | ||
| 44 | |||
| 45 | var names = Query.GetSubStorageNames(patchPath); | ||
| 46 | Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); | ||
| 47 | |||
| 48 | var cab = Path.Combine(tempFolder, "foo.cab"); | ||
| 49 | Query.ExtractStream(patchPath, "foo.cab", cab); | ||
| 50 | Assert.True(File.Exists(cab)); | ||
| 51 | |||
| 52 | var files = Query.GetCabinetFiles(cab); | ||
| 53 | Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray()); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | [Fact] | ||
| 58 | public void CanBuildSimplePatchWithNoFileChanges() | ||
| 59 | { | ||
| 60 | var folder = TestData.Get(@"TestData\PatchNoFileChanges"); | ||
| 61 | |||
| 62 | using (var fs = new DisposableFileSystem()) | ||
| 63 | { | ||
| 64 | var tempFolder = fs.GetFolder(); | ||
| 65 | |||
| 66 | var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); | ||
| 67 | var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); | ||
| 68 | var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1", hasNoFiles: true); | ||
| 69 | var patchPath = Path.ChangeExtension(patchPdb, ".msp"); | ||
| 70 | |||
| 71 | Assert.True(File.Exists(baselinePdb)); | ||
| 72 | Assert.True(File.Exists(update1Pdb)); | ||
| 73 | |||
| 74 | var doc = GetExtractPatchXml(patchPath); | ||
| 75 | Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); | ||
| 76 | |||
| 77 | var names = Query.GetSubStorageNames(patchPath); | ||
| 78 | Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); | ||
| 79 | |||
| 80 | var cab = Path.Combine(tempFolder, "foo.cab"); | ||
| 81 | Query.ExtractStream(patchPath, "foo.cab", cab); | ||
| 82 | Assert.True(File.Exists(cab)); | ||
| 83 | |||
| 84 | var files = Query.GetCabinetFiles(cab); | ||
| 85 | Assert.Empty(files); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6387")] | ||
| 90 | public void CanBuildPatchFromProductWithFilesFromWixlib() | ||
| 91 | { | ||
| 92 | var folder = TestData.Get(@"TestData\PatchFromWixlib"); | ||
| 93 | |||
| 94 | using (var fs = new DisposableFileSystem()) | ||
| 95 | { | ||
| 96 | var tempFolderBaseline = fs.GetFolder(); | ||
| 97 | var tempFolderUpdate = fs.GetFolder(); | ||
| 98 | var tempFolderPatch = fs.GetFolder(); | ||
| 99 | |||
| 100 | var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0"); | ||
| 101 | var update1Pdb = BuildMsi("Update.msi", folder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1"); | ||
| 102 | var patchPdb = BuildMsp("Patch1.msp", folder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(update1Pdb) }, hasNoFiles: true); | ||
| 103 | var patchPath = Path.ChangeExtension(patchPdb, ".msp"); | ||
| 104 | |||
| 105 | Assert.True(File.Exists(baselinePdb)); | ||
| 106 | Assert.True(File.Exists(update1Pdb)); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | [Fact] | ||
| 111 | public void CanBuildBundleWithNonSpecificPatches() | ||
| 112 | { | ||
| 113 | var folder = TestData.Get(@"TestData\PatchNonSpecific"); | ||
| 114 | |||
| 115 | using (var fs = new DisposableFileSystem()) | ||
| 116 | { | ||
| 117 | var tempFolder = fs.GetFolder(); | ||
| 118 | |||
| 119 | var baselinePdb = BuildMsi("Baseline.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.0", "A", "B"); | ||
| 120 | var updatePdb = BuildMsi("Update.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.1", "A", "B"); | ||
| 121 | var patchAPdb = BuildMsp("PatchA.msp", Path.Combine(folder, "PatchA"), tempFolder, "1.0.1", hasNoFiles: true); | ||
| 122 | var patchBPdb = BuildMsp("PatchB.msp", Path.Combine(folder, "PatchB"), tempFolder, "1.0.1", hasNoFiles: true); | ||
| 123 | var patchCPdb = BuildMsp("PatchC.msp", Path.Combine(folder, "PatchC"), tempFolder, "1.0.1", hasNoFiles: true); | ||
| 124 | var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); | ||
| 125 | var bundleBPdb = BuildBundle("BundleB.exe", Path.Combine(folder, "BundleB"), tempFolder); | ||
| 126 | var bundleCPdb = BuildBundle("BundleC.exe", Path.Combine(folder, "BundleC"), tempFolder); | ||
| 127 | |||
| 128 | VerifyPatchTargetCodes(bundleAPdb, new[] | ||
| 129 | { | ||
| 130 | "<PatchTargetCode TargetCode='{26309973-0A5E-4979-B142-98A6E064EDC0}' Product='yes' />", | ||
| 131 | }); | ||
| 132 | VerifyPatchTargetCodes(bundleBPdb, new[] | ||
| 133 | { | ||
| 134 | "<PatchTargetCode TargetCode='{26309973-0A5E-4979-B142-98A6E064EDC0}' Product='yes' />", | ||
| 135 | "<PatchTargetCode TargetCode='{32B0396A-CE36-4570-B16E-F88FA42DC409}' Product='no' />", | ||
| 136 | }); | ||
| 137 | VerifyPatchTargetCodes(bundleCPdb, new string[0]); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | [Fact] | ||
| 142 | public void CanBuildBundleWithSlipstreamPatch() | ||
| 143 | { | ||
| 144 | var folder = TestData.Get(@"TestData\PatchSingle"); | ||
| 145 | |||
| 146 | using (var fs = new DisposableFileSystem()) | ||
| 147 | { | ||
| 148 | var tempFolder = fs.GetFolder(); | ||
| 149 | |||
| 150 | var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); | ||
| 151 | var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); | ||
| 152 | var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); | ||
| 153 | var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); | ||
| 154 | |||
| 155 | using (var wixOutput = WixOutput.Read(bundleAPdb)) | ||
| 156 | { | ||
| 157 | var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); | ||
| 158 | var doc = new XmlDocument(); | ||
| 159 | doc.LoadXml(manifestData); | ||
| 160 | var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); | ||
| 161 | var slipstreamMspNodes = doc.SelectNodes("/w:BurnManifest/w:Chain/w:MsiPackage/w:SlipstreamMsp", nsmgr); | ||
| 162 | Assert.Equal(1, slipstreamMspNodes.Count); | ||
| 163 | Assert.Equal("<SlipstreamMsp Id='PatchA' />", slipstreamMspNodes[0].GetTestXml()); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | private static void VerifyPatchTargetCodes(string pdbPath, string[] expected) | ||
| 169 | { | ||
| 170 | using (var wixOutput = WixOutput.Read(pdbPath)) | ||
| 171 | { | ||
| 172 | var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); | ||
| 173 | var doc = new XmlDocument(); | ||
| 174 | doc.LoadXml(manifestData); | ||
| 175 | var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); | ||
| 176 | var patchTargetCodes = doc.SelectNodes("/w:BurnManifest/w:PatchTargetCode", nsmgr); | ||
| 177 | |||
| 178 | var actual = new List<string>(); | ||
| 179 | foreach (XmlNode patchTargetCodeNode in patchTargetCodes) | ||
| 180 | { | ||
| 181 | actual.Add(patchTargetCodeNode.GetTestXml()); | ||
| 182 | } | ||
| 183 | |||
| 184 | WixAssert.CompareLineByLine(expected, actual.ToArray()); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB) | ||
| 189 | { | ||
| 190 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 191 | var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); | ||
| 192 | |||
| 193 | var result = WixRunner.Execute(new[] | ||
| 194 | { | ||
| 195 | "build", | ||
| 196 | Path.Combine(sourceFolder, @"Package.wxs"), | ||
| 197 | "-d", "V=" + defineV, | ||
| 198 | "-d", "A=" + defineA, | ||
| 199 | "-d", "B=" + defineB, | ||
| 200 | "-bindpath", Path.Combine(sourceFolder, ".data"), | ||
| 201 | "-intermediateFolder", Path.Combine(baseFolder, "obj"), | ||
| 202 | "-o", outputPath, | ||
| 203 | "-ext", extensionPath, | ||
| 204 | }); | ||
| 205 | |||
| 206 | result.AssertSuccess(); | ||
| 207 | |||
| 208 | return Path.ChangeExtension(outputPath, ".wixpdb"); | ||
| 209 | } | ||
| 210 | |||
| 211 | private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable<string> bindpaths = null, bool hasNoFiles = false) | ||
| 212 | { | ||
| 213 | var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); | ||
| 214 | |||
| 215 | var args = new List<string> | ||
| 216 | { | ||
| 217 | "build", | ||
| 218 | hasNoFiles ? "-sw1079" : " ", | ||
| 219 | Path.Combine(sourceFolder, @"Patch.wxs"), | ||
| 220 | "-d", "V=" + defineV, | ||
| 221 | "-bindpath", Path.Combine(baseFolder, "bin"), | ||
| 222 | "-intermediateFolder", Path.Combine(baseFolder, "obj"), | ||
| 223 | "-o", outputPath | ||
| 224 | }; | ||
| 225 | |||
| 226 | foreach (var additionaBindPath in bindpaths ?? Enumerable.Empty<string>()) | ||
| 227 | { | ||
| 228 | args.Add("-bindpath"); | ||
| 229 | args.Add(additionaBindPath); | ||
| 230 | } | ||
| 231 | |||
| 232 | var result = WixRunner.Execute(args.ToArray()); | ||
| 233 | |||
| 234 | result.AssertSuccess(); | ||
| 235 | |||
| 236 | return Path.ChangeExtension(outputPath, ".wixpdb"); | ||
| 237 | } | ||
| 238 | |||
| 239 | private static string BuildBundle(string outputName, string sourceFolder, string baseFolder) | ||
| 240 | { | ||
| 241 | var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); | ||
| 242 | |||
| 243 | var result = WixRunner.Execute(new[] | ||
| 244 | { | ||
| 245 | "build", | ||
| 246 | Path.Combine(sourceFolder, @"Bundle.wxs"), | ||
| 247 | Path.Combine(sourceFolder, "..", "..", "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 248 | "-bindpath", Path.Combine(sourceFolder, "..", "..", "SimpleBundle", "data"), | ||
| 249 | "-bindpath", Path.Combine(baseFolder, "bin"), | ||
| 250 | "-intermediateFolder", Path.Combine(baseFolder, "obj"), | ||
| 251 | "-o", outputPath | ||
| 252 | }); | ||
| 253 | |||
| 254 | result.AssertSuccess(); | ||
| 255 | |||
| 256 | return Path.ChangeExtension(outputPath, ".wixpdb"); | ||
| 257 | } | ||
| 258 | |||
| 259 | private static XDocument GetExtractPatchXml(string path) | ||
| 260 | { | ||
| 261 | var buffer = new StringBuilder(65535); | ||
| 262 | var size = buffer.Capacity; | ||
| 263 | |||
| 264 | var er = MsiExtractPatchXMLData(path, 0, buffer, ref size); | ||
| 265 | if (er != 0) | ||
| 266 | { | ||
| 267 | throw new Win32Exception(er); | ||
| 268 | } | ||
| 269 | |||
| 270 | return XDocument.Parse(buffer.ToString()); | ||
| 271 | } | ||
| 272 | |||
| 273 | [DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
| 274 | private static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData); | ||
| 275 | |||
| 276 | [DllImport("msi.dll", EntryPoint = "MsiApplyPatchW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
| 277 | private static extern int MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine); | ||
| 278 | } | ||
| 279 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs new file mode 100644 index 00000000..23f6a9ba --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs | |||
| @@ -0,0 +1,212 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Xml; | ||
| 10 | using WixBuildTools.TestSupport; | ||
| 11 | using WixToolset.Core; | ||
| 12 | using WixToolset.Core.TestPackage; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Symbols; | ||
| 15 | using Xunit; | ||
| 16 | |||
| 17 | public class PayloadFixture | ||
| 18 | { | ||
| 19 | [Fact] | ||
| 20 | public void CanParseValidName() | ||
| 21 | { | ||
| 22 | var folder = TestData.Get(@"TestData\Payload"); | ||
| 23 | |||
| 24 | using (var fs = new DisposableFileSystem()) | ||
| 25 | { | ||
| 26 | var baseFolder = fs.GetFolder(); | ||
| 27 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 28 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 29 | |||
| 30 | var result = WixRunner.Execute(new[] | ||
| 31 | { | ||
| 32 | "build", | ||
| 33 | Path.Combine(folder, "ValidName.wxs"), | ||
| 34 | "-intermediateFolder", intermediateFolder, | ||
| 35 | "-o", wixlibPath, | ||
| 36 | }); | ||
| 37 | |||
| 38 | result.AssertSuccess(); | ||
| 39 | |||
| 40 | Assert.Empty(result.Messages); | ||
| 41 | |||
| 42 | var intermediate = Intermediate.Load(wixlibPath); | ||
| 43 | var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); | ||
| 44 | var payloadSymbol = allSymbols.OfType<WixBundlePayloadSymbol>() | ||
| 45 | .SingleOrDefault(); | ||
| 46 | Assert.NotNull(payloadSymbol); | ||
| 47 | |||
| 48 | var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool | ||
| 49 | ? field.AsNullableNumber()?.ToString() | ||
| 50 | : field?.AsString()) | ||
| 51 | .ToList(); | ||
| 52 | Assert.Equal(@"dir\file.ext", fields[(int)WixBundlePayloadSymbolFields.Name]); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | [Fact] | ||
| 57 | public void CanCanonicalizeName() | ||
| 58 | { | ||
| 59 | var folder = TestData.Get(@"TestData\Payload"); | ||
| 60 | |||
| 61 | using (var fs = new DisposableFileSystem()) | ||
| 62 | { | ||
| 63 | var baseFolder = fs.GetFolder(); | ||
| 64 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 65 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 66 | |||
| 67 | var result = WixRunner.Execute(warningsAsErrors: false, new[] | ||
| 68 | { | ||
| 69 | "build", | ||
| 70 | Path.Combine(folder, "CanonicalizeName.wxs"), | ||
| 71 | "-intermediateFolder", intermediateFolder, | ||
| 72 | "-o", wixlibPath, | ||
| 73 | }); | ||
| 74 | |||
| 75 | result.AssertSuccess(); | ||
| 76 | |||
| 77 | Assert.Single(result.Messages, m => m.Id == (int)WarningMessages.Ids.PathCanonicalized); | ||
| 78 | |||
| 79 | var intermediate = Intermediate.Load(wixlibPath); | ||
| 80 | var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); | ||
| 81 | var payloadSymbol = allSymbols.OfType<WixBundlePayloadSymbol>() | ||
| 82 | .SingleOrDefault(); | ||
| 83 | Assert.NotNull(payloadSymbol); | ||
| 84 | |||
| 85 | var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool | ||
| 86 | ? field.AsNullableNumber()?.ToString() | ||
| 87 | : field?.AsString()) | ||
| 88 | .ToList(); | ||
| 89 | Assert.Equal(@"c\d.exe", fields[(int)WixBundlePayloadSymbolFields.Name]); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | [Fact] | ||
| 94 | public void RejectsAbsoluteName() | ||
| 95 | { | ||
| 96 | var folder = TestData.Get(@"TestData\Payload"); | ||
| 97 | |||
| 98 | using (var fs = new DisposableFileSystem()) | ||
| 99 | { | ||
| 100 | var baseFolder = fs.GetFolder(); | ||
| 101 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 102 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 103 | |||
| 104 | var result = WixRunner.Execute(new[] | ||
| 105 | { | ||
| 106 | "build", | ||
| 107 | Path.Combine(folder, "AbsoluteName.wxs"), | ||
| 108 | "-intermediateFolder", intermediateFolder, | ||
| 109 | "-o", wixlibPath, | ||
| 110 | }); | ||
| 111 | |||
| 112 | Assert.InRange(result.ExitCode, 2, int.MaxValue); | ||
| 113 | |||
| 114 | var expectedIllegalRelativeLongFileName = 1; | ||
| 115 | var expectedPayloadMustBeRelativeToCache = 2; | ||
| 116 | Assert.Equal(expectedIllegalRelativeLongFileName, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.IllegalRelativeLongFilename).Count()); | ||
| 117 | Assert.Equal(expectedPayloadMustBeRelativeToCache, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.PayloadMustBeRelativeToCache).Count()); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | [Fact] | ||
| 122 | public void RejectsPayloadSharedBetweenPackageAndBA() | ||
| 123 | { | ||
| 124 | var folder = TestData.Get(@"TestData"); | ||
| 125 | |||
| 126 | using (var fs = new DisposableFileSystem()) | ||
| 127 | { | ||
| 128 | var baseFolder = fs.GetFolder(); | ||
| 129 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 130 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 131 | |||
| 132 | var result = WixRunner.Execute(new[] | ||
| 133 | { | ||
| 134 | "build", | ||
| 135 | Path.Combine(folder, "Payload", "SharedBAAndPackagePayloadBundle.wxs"), | ||
| 136 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 137 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 138 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 139 | "-intermediateFolder", intermediateFolder, | ||
| 140 | "-o", bundlePath, | ||
| 141 | }); | ||
| 142 | |||
| 143 | Assert.Equal((int)LinkerErrors.Ids.PayloadSharedWithBA, result.ExitCode); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | [Fact] | ||
| 148 | public void ReplacesDownloadUrlPlaceholders() | ||
| 149 | { | ||
| 150 | var folder = TestData.Get(@"TestData"); | ||
| 151 | |||
| 152 | using (var fs = new DisposableFileSystem()) | ||
| 153 | { | ||
| 154 | var baseFolder = fs.GetFolder(); | ||
| 155 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 156 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 157 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 158 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 159 | |||
| 160 | var result = WixRunner.Execute(false, new[] | ||
| 161 | { | ||
| 162 | "build", | ||
| 163 | Path.Combine(folder, "Payload", "DownloadUrlPlaceholdersBundle.wxs"), | ||
| 164 | Path.Combine(folder, "SimpleBundle", "MultiFileBootstrapperApplication.wxs"), | ||
| 165 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 166 | "-bindpath", Path.Combine(folder, ".Data"), | ||
| 167 | "-intermediateFolder", intermediateFolder, | ||
| 168 | "-o", bundlePath, | ||
| 169 | }); | ||
| 170 | |||
| 171 | result.AssertSuccess(); | ||
| 172 | |||
| 173 | WixAssert.CompareLineByLine(new string[] | ||
| 174 | { | ||
| 175 | "The Payload 'burn.exe' is being added to Container 'PackagesContainer', overriding its Compressed value of 'no'.", | ||
| 176 | }, result.Messages.Select(m => m.ToString()).ToArray()); | ||
| 177 | |||
| 178 | Assert.True(File.Exists(bundlePath)); | ||
| 179 | |||
| 180 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 181 | extractResult.AssertSuccess(); | ||
| 182 | |||
| 183 | var ignoreAttributesByElementName = new Dictionary<string, List<string>> | ||
| 184 | { | ||
| 185 | { "Container", new List<string> { "FileSize", "Hash" } }, | ||
| 186 | { "Payload", new List<string> { "FileSize", "Hash" } }, | ||
| 187 | }; | ||
| 188 | var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload") | ||
| 189 | .Cast<XmlElement>() | ||
| 190 | .Select(e => e.GetTestXml(ignoreAttributesByElementName)) | ||
| 191 | .ToArray(); | ||
| 192 | WixAssert.CompareLineByLine(new string[] | ||
| 193 | { | ||
| 194 | "<Payload Id='burn.exe' FilePath='burn.exe' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a0' Container='PackagesContainer' />", | ||
| 195 | "<Payload Id='test.msi' FilePath='test.msi' FileSize='*' Hash='*' DownloadUrl='http://example.com/id/test.msi/test.msi' Packaging='external' SourcePath='test.msi' />", | ||
| 196 | "<Payload Id='LayoutOnlyPayload' FilePath='DownloadUrlPlaceholdersBundle.wxs' FileSize='*' Hash='*' LayoutOnly='yes' DownloadUrl='http://example.com/id/LayoutOnlyPayload/DownloadUrlPlaceholdersBundle.wxs' Packaging='external' SourcePath='DownloadUrlPlaceholdersBundle.wxs' />", | ||
| 197 | @"<Payload Id='fhuZsOcBDTuIX8rF96kswqI6SnuI' FilePath='MsiPackage\test.txt' FileSize='*' Hash='*' Packaging='external' SourcePath='MsiPackage\test.txt' />", | ||
| 198 | @"<Payload Id='faf_OZ741BG7SJ6ZkcIvivZ2Yzo8' FilePath='MsiPackage\Shared.dll' FileSize='*' Hash='*' Packaging='external' SourcePath='MsiPackage\Shared.dll' />", | ||
| 199 | }, payloads); | ||
| 200 | |||
| 201 | var containers = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Container") | ||
| 202 | .Cast<XmlElement>() | ||
| 203 | .Select(e => e.GetTestXml(ignoreAttributesByElementName)) | ||
| 204 | .ToArray(); | ||
| 205 | WixAssert.CompareLineByLine(new string[] | ||
| 206 | { | ||
| 207 | "<Container Id='PackagesContainer' FileSize='*' Hash='*' DownloadUrl='http://example.com/id/PackagesContainer/packages.cab' FilePath='packages.cab' />", | ||
| 208 | }, containers); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs new file mode 100644 index 00000000..ae8a1bcc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs | |||
| @@ -0,0 +1,181 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Linq; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core; | ||
| 9 | using WixToolset.Core.TestPackage; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | using WixToolset.Extensibility.Data; | ||
| 13 | using WixToolset.Extensibility.Services; | ||
| 14 | using Xunit; | ||
| 15 | |||
| 16 | public class PreprocessorFixture | ||
| 17 | { | ||
| 18 | [Fact] | ||
| 19 | public void PreprocessDirectly() | ||
| 20 | { | ||
| 21 | var folder = TestData.Get(@"TestData\IncludePath"); | ||
| 22 | var sourcePath = Path.Combine(folder, "Package.wxs"); | ||
| 23 | var includeFolder = Path.Combine(folder, "data"); | ||
| 24 | var includeFile = Path.Combine(includeFolder, "Package.wxi"); | ||
| 25 | |||
| 26 | var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); | ||
| 27 | |||
| 28 | var context = serviceProvider.GetService<IPreprocessContext>(); | ||
| 29 | context.SourcePath = sourcePath; | ||
| 30 | context.IncludeSearchPaths = new[] { includeFolder }; | ||
| 31 | |||
| 32 | var preprocessor = serviceProvider.GetService<IPreprocessor>(); | ||
| 33 | var result = preprocessor.Preprocess(context); | ||
| 34 | |||
| 35 | var includedFile = result.IncludedFiles.Single(); | ||
| 36 | Assert.NotNull(result.Document); | ||
| 37 | Assert.Equal(includeFile, includedFile.Path); | ||
| 38 | Assert.Equal(sourcePath, includedFile.SourceLineNumbers.FileName); | ||
| 39 | Assert.Equal(1, includedFile.SourceLineNumbers.LineNumber.Value); | ||
| 40 | Assert.Equal($"{sourcePath}*1", includedFile.SourceLineNumbers.QualifiedFileName); | ||
| 41 | Assert.Null(includedFile.SourceLineNumbers.Parent); | ||
| 42 | } | ||
| 43 | |||
| 44 | [Fact] | ||
| 45 | public void IncludeSourceLineNumbersPreserved() | ||
| 46 | { | ||
| 47 | var folder = TestData.Get(@"TestData\IncludePath"); | ||
| 48 | |||
| 49 | using (var fs = new DisposableFileSystem()) | ||
| 50 | { | ||
| 51 | var baseFolder = fs.GetFolder(); | ||
| 52 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 53 | |||
| 54 | var result = WixRunner.Execute(warningsAsErrors: false, new[] | ||
| 55 | { | ||
| 56 | "build", | ||
| 57 | Path.Combine(folder, "Package.wxs"), | ||
| 58 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 59 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 60 | "-includepath", Path.Combine(folder, "data"), | ||
| 61 | "-bindpath", Path.Combine(folder, "data"), | ||
| 62 | "-intermediateFolder", intermediateFolder, | ||
| 63 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 64 | }); | ||
| 65 | |||
| 66 | result.AssertSuccess(); | ||
| 67 | |||
| 68 | using (var output = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) | ||
| 69 | { | ||
| 70 | var intermediate = Intermediate.Load(output); | ||
| 71 | var component = intermediate.Sections.Single().Symbols.OfType<ComponentSymbol>().Single(); | ||
| 72 | Assert.Equal(3, component.SourceLineNumbers.LineNumber); | ||
| 73 | Assert.Equal(5, component.SourceLineNumbers.Parent.LineNumber); | ||
| 74 | |||
| 75 | var encoded = component.SourceLineNumbers.GetEncoded(); | ||
| 76 | var decoded = SourceLineNumber.CreateFromEncoded(encoded); | ||
| 77 | Assert.Equal(3, decoded.LineNumber); | ||
| 78 | Assert.Equal(5, decoded.Parent.LineNumber); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | [Fact] | ||
| 84 | /// <remarks> | ||
| 85 | /// This test will fail on 32-bit operating systems because it depends on "CommonProgramFiles(x86)" | ||
| 86 | /// which is only defined on 64-bit Windows. | ||
| 87 | /// </remarks> | ||
| 88 | public void SupportParensInEnvironmentVariables() | ||
| 89 | { | ||
| 90 | var folder = TestData.Get(@"TestData", "Preprocessor"); | ||
| 91 | |||
| 92 | var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); | ||
| 93 | var context = serviceProvider.GetService<IPreprocessContext>(); | ||
| 94 | context.SourcePath = Path.Combine(folder, "EnvParens.wxs"); | ||
| 95 | |||
| 96 | var preprocessor = serviceProvider.GetService<IPreprocessor>(); | ||
| 97 | var result = preprocessor.Preprocess(context); | ||
| 98 | Assert.NotNull(result.Document); | ||
| 99 | } | ||
| 100 | |||
| 101 | [Fact] | ||
| 102 | public void VariableRedefinitionIsAWarning() | ||
| 103 | { | ||
| 104 | var folder = TestData.Get(@"TestData\Variables"); | ||
| 105 | |||
| 106 | using (var fs = new DisposableFileSystem()) | ||
| 107 | { | ||
| 108 | var baseFolder = fs.GetFolder(); | ||
| 109 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 110 | |||
| 111 | var result = WixRunner.Execute(warningsAsErrors: false, new[] | ||
| 112 | { | ||
| 113 | "build", | ||
| 114 | Path.Combine(folder, "Package.wxs"), | ||
| 115 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 116 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 117 | "-bindpath", Path.Combine(folder, "data"), | ||
| 118 | "-intermediateFolder", intermediateFolder, | ||
| 119 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 120 | }); | ||
| 121 | |||
| 122 | result.AssertSuccess(); | ||
| 123 | |||
| 124 | var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.VariableDeclarationCollision); | ||
| 125 | Assert.Single(warning); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | [Fact] | ||
| 130 | public void ForEachLoopsWork() | ||
| 131 | { | ||
| 132 | var folder = TestData.Get(@"TestData\ForEach"); | ||
| 133 | |||
| 134 | using (var fs = new DisposableFileSystem()) | ||
| 135 | { | ||
| 136 | var baseFolder = fs.GetFolder(); | ||
| 137 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 138 | |||
| 139 | var result = WixRunner.Execute(new[] | ||
| 140 | { | ||
| 141 | "build", | ||
| 142 | Path.Combine(folder, "Package.wxs"), | ||
| 143 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 144 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 145 | "-bindpath", Path.Combine(folder, "data"), | ||
| 146 | "-intermediateFolder", intermediateFolder, | ||
| 147 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 148 | }); | ||
| 149 | |||
| 150 | result.AssertSuccess(); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | [Fact] | ||
| 155 | public void NonterminatedPreprocessorInstructionShowsSourceLineNumber() | ||
| 156 | { | ||
| 157 | var folder = TestData.Get(@"TestData\BadIf"); | ||
| 158 | |||
| 159 | using (var fs = new DisposableFileSystem()) | ||
| 160 | { | ||
| 161 | var baseFolder = fs.GetFolder(); | ||
| 162 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 163 | |||
| 164 | var result = WixRunner.Execute(new[] | ||
| 165 | { | ||
| 166 | "build", | ||
| 167 | Path.Combine(folder, "Package.wxs"), | ||
| 168 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 169 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 170 | "-bindpath", Path.Combine(folder, "data"), | ||
| 171 | "-intermediateFolder", intermediateFolder, | ||
| 172 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 173 | }); | ||
| 174 | |||
| 175 | Assert.Equal(147, result.ExitCode); | ||
| 176 | Assert.StartsWith("Found a <?if?>", result.Messages.Single().ToString()); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs new file mode 100644 index 00000000..e4d95b5d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Text; | ||
| 10 | using WixBuildTools.TestSupport; | ||
| 11 | using WixToolset.Core.TestPackage; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using Xunit; | ||
| 14 | |||
| 15 | public class RegistryFixture | ||
| 16 | { | ||
| 17 | [Fact] | ||
| 18 | public void PopulatesRegistryTableFromRegistryValue() | ||
| 19 | { | ||
| 20 | var folder = TestData.Get(@"TestData"); | ||
| 21 | |||
| 22 | using (var fs = new DisposableFileSystem()) | ||
| 23 | { | ||
| 24 | var baseFolder = fs.GetFolder(); | ||
| 25 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 26 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 27 | |||
| 28 | var result = WixRunner.Execute(new[] | ||
| 29 | { | ||
| 30 | "build", | ||
| 31 | Path.Combine(folder, "Registry", "RegistryValue.wxs"), | ||
| 32 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 33 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 34 | "-intermediateFolder", intermediateFolder, | ||
| 35 | "-o", msiPath | ||
| 36 | }); | ||
| 37 | |||
| 38 | result.AssertSuccess(); | ||
| 39 | |||
| 40 | Assert.True(File.Exists(msiPath)); | ||
| 41 | var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); | ||
| 42 | WixAssert.CompareLineByLine(new[] | ||
| 43 | { | ||
| 44 | "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent", | ||
| 45 | "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", | ||
| 46 | }, results); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | [Fact] | ||
| 51 | public void PopulatesRegistryTableFromRegistryValueMultiString() | ||
| 52 | { | ||
| 53 | var folder = TestData.Get(@"TestData"); | ||
| 54 | |||
| 55 | using (var fs = new DisposableFileSystem()) | ||
| 56 | { | ||
| 57 | var baseFolder = fs.GetFolder(); | ||
| 58 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 59 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 60 | |||
| 61 | var result = WixRunner.Execute(new[] | ||
| 62 | { | ||
| 63 | "build", | ||
| 64 | Path.Combine(folder, "Registry", "RegistryValueMultiString.wxs"), | ||
| 65 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 66 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 67 | "-intermediateFolder", intermediateFolder, | ||
| 68 | "-o", msiPath | ||
| 69 | }); | ||
| 70 | |||
| 71 | result.AssertSuccess(); | ||
| 72 | |||
| 73 | var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); | ||
| 74 | WixAssert.CompareLineByLine(new[] | ||
| 75 | { | ||
| 76 | "Registry:regitq_Wx9LfvJuNSc2un6gIHAzr4A\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMultiStringComponent", | ||
| 77 | "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~][~]c[~]\tMultiStringComponent", | ||
| 78 | }, results); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | [Fact] | ||
| 83 | public void DuplicateRegistryValueIdsAreDetectedSmoothly() | ||
| 84 | { | ||
| 85 | var folder = TestData.Get(@"TestData"); | ||
| 86 | |||
| 87 | using (var fs = new DisposableFileSystem()) | ||
| 88 | { | ||
| 89 | var baseFolder = fs.GetFolder(); | ||
| 90 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 91 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 92 | |||
| 93 | var result = WixRunner.Execute(new[] | ||
| 94 | { | ||
| 95 | "build", | ||
| 96 | Path.Combine(folder, "Registry", "DuplicateRegistryValueIds.wxs"), | ||
| 97 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 98 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 99 | "-intermediateFolder", intermediateFolder, | ||
| 100 | "-o", msiPath | ||
| 101 | }, out var messages); | ||
| 102 | |||
| 103 | Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol).Count()); | ||
| 104 | Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol2).Count()); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | [Fact] | ||
| 109 | public void PopulatesRegistryTableFromRemoveRegistryKey() | ||
| 110 | { | ||
| 111 | var folder = TestData.Get(@"TestData"); | ||
| 112 | |||
| 113 | using (var fs = new DisposableFileSystem()) | ||
| 114 | { | ||
| 115 | var baseFolder = fs.GetFolder(); | ||
| 116 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 117 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 118 | |||
| 119 | var result = WixRunner.Execute(new[] | ||
| 120 | { | ||
| 121 | "build", | ||
| 122 | Path.Combine(folder, "Registry", "RemoveRegistryKey.wxs"), | ||
| 123 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 124 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 125 | "-intermediateFolder", intermediateFolder, | ||
| 126 | "-o", msiPath | ||
| 127 | }); | ||
| 128 | |||
| 129 | result.AssertSuccess(); | ||
| 130 | |||
| 131 | Assert.True(File.Exists(msiPath)); | ||
| 132 | var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); | ||
| 133 | WixAssert.CompareLineByLine(new[] | ||
| 134 | { | ||
| 135 | "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp", | ||
| 136 | }, results); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | [Fact] | ||
| 141 | public void PopulatesRegistryTableWithoutExtraBackslash() | ||
| 142 | { | ||
| 143 | var folder = TestData.Get(@"TestData"); | ||
| 144 | |||
| 145 | using (var fs = new DisposableFileSystem()) | ||
| 146 | { | ||
| 147 | var baseFolder = fs.GetFolder(); | ||
| 148 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 149 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 150 | |||
| 151 | var result = WixRunner.Execute(new[] | ||
| 152 | { | ||
| 153 | "build", | ||
| 154 | Path.Combine(folder, "Registry", "RegistryKeyEndingWithBackslash.wxs"), | ||
| 155 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 156 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 157 | "-intermediateFolder", intermediateFolder, | ||
| 158 | "-o", msiPath | ||
| 159 | }); | ||
| 160 | |||
| 161 | result.AssertSuccess(); | ||
| 162 | |||
| 163 | Assert.True(File.Exists(msiPath)); | ||
| 164 | var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); | ||
| 165 | WixAssert.CompareLineByLine(new[] | ||
| 166 | { | ||
| 167 | "Registry:reg1\t2\tSoftware\\WBM\\WB\t*\t\tMiscComponent", | ||
| 168 | "Registry:reg2\t2\tSoftware\\WBM\\WB\tInstallationPath\t[INSTALLFOLDER]\tMiscComponent", | ||
| 169 | }, results); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs new file mode 100644 index 00000000..9e19abb0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixBuildTools.TestSupport; | ||
| 7 | using WixToolset.Core.TestPackage; | ||
| 8 | using Xunit; | ||
| 9 | |||
| 10 | public class RollbackBoundaryFixture | ||
| 11 | { | ||
| 12 | [Fact] | ||
| 13 | public void CanStartChainWithRollbackBoundary() | ||
| 14 | { | ||
| 15 | var folder = TestData.Get(@"TestData"); | ||
| 16 | |||
| 17 | using (var fs = new DisposableFileSystem()) | ||
| 18 | { | ||
| 19 | var baseFolder = fs.GetFolder(); | ||
| 20 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 21 | var exePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 22 | |||
| 23 | var result = WixRunner.Execute(new[] | ||
| 24 | { | ||
| 25 | "build", | ||
| 26 | Path.Combine(folder, "RollbackBoundary", "BeginningOfChain.wxs"), | ||
| 27 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 28 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 29 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 30 | "-intermediateFolder", intermediateFolder, | ||
| 31 | "-o", exePath, | ||
| 32 | }); | ||
| 33 | |||
| 34 | result.AssertSuccess(); | ||
| 35 | |||
| 36 | Assert.True(File.Exists(exePath)); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | } | ||
| 41 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs new file mode 100644 index 00000000..3b6c50c0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixBuildTools.TestSupport; | ||
| 7 | using WixToolset.Core.TestPackage; | ||
| 8 | using Xunit; | ||
| 9 | |||
| 10 | public class ShortcutFixture | ||
| 11 | { | ||
| 12 | [Fact] | ||
| 13 | public void CanBuildShortcutNameWithShortname() | ||
| 14 | { | ||
| 15 | var folder = TestData.Get(@"TestData"); | ||
| 16 | |||
| 17 | using (var fs = new DisposableFileSystem()) | ||
| 18 | { | ||
| 19 | var baseFolder = fs.GetFolder(); | ||
| 20 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 21 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 22 | |||
| 23 | var result = WixRunner.Execute(new[] | ||
| 24 | { | ||
| 25 | "build", | ||
| 26 | Path.Combine(folder, "Shortcut", "ShortcutSameNameShortName.wxs"), | ||
| 27 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 28 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 29 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 30 | "-intermediateFolder", intermediateFolder, | ||
| 31 | "-o", msiPath | ||
| 32 | }); | ||
| 33 | |||
| 34 | result.AssertSuccess(); | ||
| 35 | |||
| 36 | var results = Query.QueryDatabase(msiPath, new[] { "Shortcut" }); | ||
| 37 | WixAssert.CompareLineByLine(new[] | ||
| 38 | { | ||
| 39 | "Shortcut:sctzJpBYlrhdx4Mm9Xh41X0KPWYiX0\tINSTALLFOLDER\tDaName\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", | ||
| 40 | }, results); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | [Fact] | ||
| 45 | public void PopulatesMsiShortcutPropertyTable() | ||
| 46 | { | ||
| 47 | var folder = TestData.Get(@"TestData"); | ||
| 48 | |||
| 49 | using (var fs = new DisposableFileSystem()) | ||
| 50 | { | ||
| 51 | var baseFolder = fs.GetFolder(); | ||
| 52 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 53 | var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); | ||
| 54 | |||
| 55 | var result = WixRunner.Execute(new[] | ||
| 56 | { | ||
| 57 | "build", | ||
| 58 | Path.Combine(folder, "Shortcut", "ShortcutProperty.wxs"), | ||
| 59 | Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), | ||
| 60 | Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), | ||
| 61 | "-bindpath", Path.Combine(folder, "SingleFile", "data"), | ||
| 62 | "-intermediateFolder", intermediateFolder, | ||
| 63 | "-o", msiPath | ||
| 64 | }); | ||
| 65 | |||
| 66 | result.AssertSuccess(); | ||
| 67 | |||
| 68 | Assert.True(File.Exists(msiPath)); | ||
| 69 | var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" }); | ||
| 70 | WixAssert.CompareLineByLine(new[] | ||
| 71 | { | ||
| 72 | "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", | ||
| 73 | "Shortcut:TheShortcut\tINSTALLFOLDER\td\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", | ||
| 74 | }, results); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs new file mode 100644 index 00000000..15276b18 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Linq; | ||
| 7 | using System.Xml.Linq; | ||
| 8 | using WixBuildTools.TestSupport; | ||
| 9 | using WixToolset.Core.TestPackage; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using Xunit; | ||
| 12 | |||
| 13 | public class SoftwareTagFixture | ||
| 14 | { | ||
| 15 | private static readonly XNamespace BurnManifestNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; | ||
| 16 | private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"; | ||
| 17 | |||
| 18 | [Fact] | ||
| 19 | public void CanBuildPackageWithTag() | ||
| 20 | { | ||
| 21 | var folder = TestData.Get(@"TestData\ProductTag"); | ||
| 22 | var build = new Builder(folder, null, new[] { folder }); | ||
| 23 | |||
| 24 | var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag"); | ||
| 25 | |||
| 26 | var replacePackageCodeStart = results[2].IndexOf("\tmsi:package/") + "\tmsi:package/".Length; | ||
| 27 | var replacePackageCodeEnd = results[2].IndexOf("\t", replacePackageCodeStart); | ||
| 28 | results[2] = results[2].Substring(0, replacePackageCodeStart) + "???" + results[2].Substring(replacePackageCodeEnd); | ||
| 29 | WixAssert.CompareLineByLine(new[] | ||
| 30 | { | ||
| 31 | "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1", | ||
| 32 | "File:tagEYRYWwOt95punO7qPPAQ9p1GBpY\ttagEYRYWwOt95punO7qPPAQ9p1GBpY\trdcfonyt.swi|~TagTestPackage.swidtag\t449\t\t\t1\t2", | ||
| 33 | "SoftwareIdentificationTag:tagEYRYWwOt95punO7qPPAQ9p1GBpY\twixtoolset.org\tmsi:package/???\tmsi:upgrade/047730A5-30FE-4A62-A520-DA9381B8226A\t" | ||
| 34 | }, results.ToArray()); | ||
| 35 | } | ||
| 36 | |||
| 37 | [Fact] | ||
| 38 | public void CanBuildBundleWithTag() | ||
| 39 | { | ||
| 40 | var testDataFolder = TestData.Get(@"TestData"); | ||
| 41 | |||
| 42 | using (var fs = new DisposableFileSystem()) | ||
| 43 | { | ||
| 44 | var baseFolder = fs.GetFolder(); | ||
| 45 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 46 | |||
| 47 | var result = WixRunner.Execute(new[] | ||
| 48 | { | ||
| 49 | "build", | ||
| 50 | Path.Combine(testDataFolder, "ProductTag", "PackageWithTag.wxs"), | ||
| 51 | Path.Combine(testDataFolder, "ProductTag", "PackageComponents.wxs"), | ||
| 52 | "-loc", Path.Combine(testDataFolder, "ProductTag", "Package.en-us.wxl"), | ||
| 53 | "-bindpath", Path.Combine(testDataFolder, "ProductTag"), | ||
| 54 | "-intermediateFolder", Path.Combine(intermediateFolder, "package"), | ||
| 55 | "-o", Path.Combine(baseFolder, "package", @"test.msi") | ||
| 56 | }); | ||
| 57 | |||
| 58 | result.AssertSuccess(); | ||
| 59 | |||
| 60 | result = WixRunner.Execute(new[] | ||
| 61 | { | ||
| 62 | "build", | ||
| 63 | Path.Combine(testDataFolder, "BundleTag", "BundleWithTag.wxs"), | ||
| 64 | "-bindpath", Path.Combine(testDataFolder, "BundleTag"), | ||
| 65 | "-bindpath", Path.Combine(baseFolder, "package"), | ||
| 66 | "-intermediateFolder", intermediateFolder, | ||
| 67 | "-o", Path.Combine(baseFolder, @"bin\test.exe") | ||
| 68 | }); | ||
| 69 | |||
| 70 | result.AssertSuccess(); | ||
| 71 | |||
| 72 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); | ||
| 73 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 74 | |||
| 75 | using (var ouput = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) | ||
| 76 | { | ||
| 77 | var badata = ouput.GetDataStream("wix-burndata.xml"); | ||
| 78 | var doc = XDocument.Load(badata); | ||
| 79 | |||
| 80 | var swidTag = doc.Root.Element(BurnManifestNamespace + "Registration").Element(BurnManifestNamespace + "SoftwareTag").Value; | ||
| 81 | |||
| 82 | var swidTagPath = Path.Combine(baseFolder, "test.swidtag"); | ||
| 83 | File.WriteAllText(swidTagPath, swidTag); | ||
| 84 | |||
| 85 | var docTag = XDocument.Load(swidTagPath); | ||
| 86 | var title = docTag.Root.Attribute("name").Value; | ||
| 87 | var version = docTag.Root.Attribute("version").Value; | ||
| 88 | Assert.Equal("~TagTestBundle", title); | ||
| 89 | Assert.Equal("4.3.2.1", version); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | private static void Build(string[] args) | ||
| 95 | { | ||
| 96 | var result = WixRunner.Execute(args) | ||
| 97 | .AssertSuccess(); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe new file mode 100644 index 00000000..2a4f423f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs new file mode 100644 index 00000000..b34c547d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="AppIdComp" Directory="INSTALLFOLDER" Guid="171BEE79-43CC-4A28-892F-7EFAC696FA4B"> | ||
| 6 | <File Id="AppIdComp.txt" Source="test.txt" Name="AppIdComp.txt"></File> | ||
| 7 | <AppId Id="D6040299-B15C-4C94-AE26-0C9B60D14C35" Advertise="yes" /> | ||
| 8 | </Component> | ||
| 9 | </ComponentGroup> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs new file mode 100644 index 00000000..4dd701f0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <Property Id="SAMPLECOMPFOUND"> | ||
| 9 | <ComponentSearch Id="SampleCompSearch" Guid="{4D9A0D20-D0CC-40DE-B580-EAD38B985217}"></ComponentSearch> | ||
| 10 | </Property> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs new file mode 100644 index 00000000..6b9fe013 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{33C58183-7333-4257-AEFD-6705DA66E617}"> | ||
| 3 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 4 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="ykd0udtb"> | ||
| 5 | <Component Id="test.txt" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always32"> | ||
| 6 | <File Id="test.txt" Name="test.txt" KeyPath="yes" Source="MsiPackage\test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </Directory> | ||
| 9 | </StandardDirectory> | ||
| 10 | <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle"> | ||
| 11 | <ComponentRef Id="test.txt" /> | ||
| 12 | </Feature> | ||
| 13 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 14 | <Media Id="1" /> | ||
| 15 | <Property Id="SAMPLEREGFOUND"> | ||
| 16 | <RegistrySearch Id="RegSearch" Root="HKLM" Key="Reg" Type="raw" Bitness="always32" /> | ||
| 17 | </Property> | ||
| 18 | <Property Id="NESTEDDIRFOUND"> | ||
| 19 | <RegistrySearch Id="ARegKeySearch" Root="HKLM" Key="ARegKey" Type="raw" Bitness="always32"> | ||
| 20 | <DirectorySearch Id="TopDirSearch" Path="TopDir"> | ||
| 21 | <DirectorySearch Id="SecondDirSearch" Path="SecondDir"> | ||
| 22 | <DirectorySearch Id="ThirdDirSearch" Path="ThirdDir"> | ||
| 23 | <DirectorySearch Id="FourthDirSearch" Path="FourthDir" /> | ||
| 24 | </DirectorySearch> | ||
| 25 | </DirectorySearch> | ||
| 26 | </DirectorySearch> | ||
| 27 | </RegistrySearch> | ||
| 28 | </Property> | ||
| 29 | <Property Id="SAMPLENESTEDDIRFOUND"> | ||
| 30 | <RegistrySearch Id="NestedRegSearch" Root="HKLM" Key="NestedReg" Type="raw" Bitness="always32"> | ||
| 31 | <DirectorySearch Id="SampleNestedDirSearch" Path="NestedDir" /> | ||
| 32 | </RegistrySearch> | ||
| 33 | </Property> | ||
| 34 | <Property Id="SAMPLEDIRFOUND"> | ||
| 35 | <RegistrySearch Id="SubRegSearch" Root="HKLM" Key="SampleReg" Type="raw" Bitness="always32"> | ||
| 36 | <DirectorySearch Id="SampleDirSearch" Path="SampleDir"> | ||
| 37 | <DirectorySearch Id="SubDirSearch" Path="Subdir" /> | ||
| 38 | </DirectorySearch> | ||
| 39 | </RegistrySearch> | ||
| 40 | </Property> | ||
| 41 | </Package> | ||
| 42 | </Wix> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs new file mode 100644 index 00000000..e255c83d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <Property Id="SAMPLEDIRFOUND"> | ||
| 9 | <DirectorySearch Id="SampleDirSearch" AssignToProperty="yes" Path="C:\SampleDir"></DirectorySearch> | ||
| 10 | </Property> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs new file mode 100644 index 00000000..c17d9848 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <Property Id="SAMPLEFILEFOUND"> | ||
| 9 | <IniFileSearch Id="SampleIniFileSearch" Name="sample.fil" Section="MySection" Key="MyKey"> | ||
| 10 | <FileSearch Id="SampleFileSearch" Name="sample.fil"></FileSearch> | ||
| 11 | </IniFileSearch> | ||
| 12 | </Property> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi new file mode 100644 index 00000000..ea1296c3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs new file mode 100644 index 00000000..f800264d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <Property Id="SAMPLEREGFOUND"> | ||
| 9 | <RegistrySearch Id="SampleRegSearch" Root="HKLM" Key="SampleReg" Type="raw"></RegistrySearch> | ||
| 10 | </Property> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs new file mode 100644 index 00000000..8be5abb2 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <Property Id="SAMPLEREGFOUND"> | ||
| 9 | <RegistrySearch Id="SampleRegSearch" Root="HKLM" Key="SampleReg" Type="raw" Bitness="always64"></RegistrySearch> | ||
| 10 | </Property> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs new file mode 100644 index 00000000..c345305d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="AssemblyMsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 9 | </Feature> | ||
| 10 | </Package> | ||
| 11 | |||
| 12 | <Fragment> | ||
| 13 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 14 | <Directory Id="INSTALLFOLDER" Name="AssemblyMsiPackage" /> | ||
| 15 | </StandardDirectory> | ||
| 16 | </Fragment> | ||
| 17 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs new file mode 100644 index 00000000..e0c84c63 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="candle.exe" Assembly=".net" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs new file mode 100644 index 00000000..45cc7114 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Directory="INSTALLFOLDER"> | ||
| 6 | <File Id="test.txt" Source="test.txt" Assembly="win32" AssemblyManifest="test.dll.manifest" /> | ||
| 7 | </Component> | ||
| 8 | <Component Id="test.dll.manifest" Directory="INSTALLFOLDER"> | ||
| 9 | <File Id="test.dll.manifest" Source="test.manifest" Name="test.dll.manifest"></File> | ||
| 10 | </Component> | ||
| 11 | </ComponentGroup> | ||
| 12 | </Fragment> | ||
| 13 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe new file mode 100644 index 00000000..18129b73 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest new file mode 100644 index 00000000..0da1f6d0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> | ||
| 3 | <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/> | ||
| 4 | <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> | ||
| 5 | <security> | ||
| 6 | <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"> | ||
| 7 | <!-- UAC Manifest Options | ||
| 8 | If you want to change the Windows User Account Control level replace the | ||
| 9 | requestedExecutionLevel node with one of the following. | ||
| 10 | |||
| 11 | <requestedExecutionLevel level="asInvoker" uiAccess="false" /> | ||
| 12 | <requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> | ||
| 13 | <requestedExecutionLevel level="highestAvailable" uiAccess="false" /> | ||
| 14 | |||
| 15 | Specifying requestedExecutionLevel element will disable file and registry virtualization. | ||
| 16 | Remove this element if your application requires this virtualization for backwards | ||
| 17 | compatibility. | ||
| 18 | --> | ||
| 19 | <requestedExecutionLevel level="asInvoker" uiAccess="false" /> | ||
| 20 | </requestedPrivileges> | ||
| 21 | </security> | ||
| 22 | </trustInfo> | ||
| 23 | |||
| 24 | <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> | ||
| 25 | <application> | ||
| 26 | <!-- A list of the Windows versions that this application has been tested on | ||
| 27 | and is designed to work with. Uncomment the appropriate elements | ||
| 28 | and Windows will automatically select the most compatible environment. --> | ||
| 29 | |||
| 30 | <!-- Windows Vista --> | ||
| 31 | <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />--> | ||
| 32 | |||
| 33 | <!-- Windows 7 --> | ||
| 34 | <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />--> | ||
| 35 | |||
| 36 | <!-- Windows 8 --> | ||
| 37 | <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />--> | ||
| 38 | |||
| 39 | <!-- Windows 8.1 --> | ||
| 40 | <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />--> | ||
| 41 | |||
| 42 | <!-- Windows 10 --> | ||
| 43 | <!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />--> | ||
| 44 | |||
| 45 | </application> | ||
| 46 | </compatibility> | ||
| 47 | |||
| 48 | <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher | ||
| 49 | DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need | ||
| 50 | to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should | ||
| 51 | also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. --> | ||
| 52 | <!-- | ||
| 53 | <application xmlns="urn:schemas-microsoft-com:asm.v3"> | ||
| 54 | <windowsSettings> | ||
| 55 | <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> | ||
| 56 | </windowsSettings> | ||
| 57 | </application> | ||
| 58 | --> | ||
| 59 | |||
| 60 | <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) --> | ||
| 61 | <!-- | ||
| 62 | <dependency> | ||
| 63 | <dependentAssembly> | ||
| 64 | <assemblyIdentity | ||
| 65 | type="win32" | ||
| 66 | name="Microsoft.Windows.Common-Controls" | ||
| 67 | version="6.0.0.0" | ||
| 68 | processorArchitecture="*" | ||
| 69 | publicKeyToken="6595b64144ccf1df" | ||
| 70 | language="*" | ||
| 71 | /> | ||
| 72 | </dependentAssembly> | ||
| 73 | </dependency> | ||
| 74 | --> | ||
| 75 | |||
| 76 | </assembly> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs new file mode 100644 index 00000000..3caa20ff --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" | ||
| 3 | xmlns:ex="http://www.example.com/scheams/v1/wxs"> | ||
| 4 | <Fragment> | ||
| 5 | <ComponentGroup Id="ProductComponents"> | ||
| 6 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 7 | </ComponentGroup> | ||
| 8 | |||
| 9 | <ex:ExampleEnsureTable /> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs new file mode 100644 index 00000000..1d7ebb94 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Language="1033" Version="1.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <ComponentGroupRef Id="ProductComponents.x86" /> | ||
| 9 | <!-- <?if x64 == $(sys.BUILDARCH) ?> --> | ||
| 10 | <!-- <?if arm==$(sys.BUILDARCH) ?> --> | ||
| 11 | <?if $(sys.BUILDARCH) = "x64" ?> | ||
| 12 | <ComponentGroupRef Id="ProductComponents.x64" /> | ||
| 13 | <ComponentGroupRef Id="ProductComponents.arm" /> | ||
| 14 | </Feature> | ||
| 15 | </Package> | ||
| 16 | |||
| 17 | <Fragment> | ||
| 18 | <Directory Id="TARGETDIR" Name="SourceDir"> | ||
| 19 | <Directory Id="ProgramFilesFolder"> | ||
| 20 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 21 | </Directory> | ||
| 22 | </Directory> | ||
| 23 | </Fragment> | ||
| 24 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs new file mode 100644 index 00000000..2a75e3d7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <?foreach ComponentPlatform in x86;x64;arm ?> | ||
| 4 | <Fragment> | ||
| 5 | <ComponentGroup Id="ProductComponents.$(var.ComponentPlatform)" Directory="INSTALLFOLDER"> | ||
| 6 | <Component> | ||
| 7 | <File Name="$(var.ComponentPlatform).dll" Source="test.txt" /> | ||
| 8 | </Component> | ||
| 9 | </ComponentGroup> | ||
| 10 | </Fragment> | ||
| 11 | <?endforeach?> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs new file mode 100644 index 00000000..a2d49b18 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <Variable Name="BadType" Type="doesnotexist" Value="dne" /> | ||
| 5 | </Fragment> | ||
| 6 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs new file mode 100644 index 00000000..0c350042 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage Id="Manual1" SourceFile="burn.exe" Name="manual1\burn.exe" DetectCondition="test" CacheId="!(wix.WixVariable1)" /> | ||
| 6 | <ExePackage Id="Manual2" SourceFile="burn.exe" Name="manual2\burn.exe" DetectCondition="test" CacheId="!(wix.WixVariable2)" /> | ||
| 7 | </PackageGroup> | ||
| 8 | |||
| 9 | <WixVariable Id="WixVariable1" Value="CollidingCacheId" /> | ||
| 10 | <WixVariable Id="WixVariable2" Value="CollidingCacheId" /> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs new file mode 100644 index 00000000..4fe7e097 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage Id="Auto1" SourceFile="burn.exe" CacheId="Auto1" DetectCondition="none" /> | ||
| 6 | <ExePackage Id="Auto2" SourceFile="burn.exe" CacheId="Auto2" DetectCondition="none" /> | ||
| 7 | <ExePackage Id="DuplicateCacheIds.wxs" SourceFile="$(sys.SOURCEFILEDIR)DuplicateCacheIds.wxs" Compressed="no" DetectCondition="none" Name="PayloadCollision"> | ||
| 8 | <Payload SourceFile="$(sys.SOURCEFILEDIR)BundleVariable.wxs" Compressed="no" Name="ContainerCollision" /> | ||
| 9 | </ExePackage> | ||
| 10 | <ExePackage Id="HiddenPersistedBundleVariable.wxs" SourceFile="$(sys.SOURCEFILEDIR)HiddenPersistedBundleVariable.wxs" Compressed="no" DetectCondition="none" Name="PayloadCollision" /> | ||
| 11 | <PackageGroupRef Id="MsiPackages" /> | ||
| 12 | </PackageGroup> | ||
| 13 | |||
| 14 | <PackageGroup Id="MsiPackages"> | ||
| 15 | <MsiPackage SourceFile="test.msi"> | ||
| 16 | <Payload SourceFile="$(sys.SOURCEFILEDIR)InvalidIds.wxs" Name="MsiPackage\test.txt" /> | ||
| 17 | <Payload SourceFile="$(sys.SOURCEFILEDIR)RegistryKey.wxs" Name="test.msi" /> | ||
| 18 | </MsiPackage> | ||
| 19 | </PackageGroup> | ||
| 20 | |||
| 21 | <Container Id="MsiPackagesContainer" Type="detached" Name="ContainerCollision"> | ||
| 22 | <PackageGroupRef Id="MsiPackages" /> | ||
| 23 | </Container> | ||
| 24 | |||
| 25 | <BootstrapperApplication> | ||
| 26 | <Payload Id="DuplicatePayloadNames.wxs" SourceFile="$(sys.SOURCEFILEPATH)" Name="fakeba.dll" /> | ||
| 27 | <Payload Id="UnscheduledPackage.wxs" SourceFile="$(sys.SOURCEFILEDIR)UnscheduledPackage.wxs" Name="BootstrapperApplicationData.xml" /> | ||
| 28 | <Payload Id="UnscheduledRollbackBoundary.wxs" SourceFile="$(sys.SOURCEFILEDIR)UnscheduledRollbackBoundary.wxs" Name="BundleExtensionData.xml" /> | ||
| 29 | </BootstrapperApplication> | ||
| 30 | </Fragment> | ||
| 31 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs new file mode 100644 index 00000000..5ebe5472 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <Variable Name="BadType" Hidden="yes" Persisted="yes" Value="dne" /> | ||
| 5 | </Fragment> | ||
| 6 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs new file mode 100644 index 00000000..78f3ebd3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <Component Id="@#$" Guid="{2F18F52A-9E24-4ebe-A5FC-974089AA03D2}" Directory="WixTestFolder"> | ||
| 5 | <CreateFolder Directory="WixTestFolder" /> | ||
| 6 | </Component> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs new file mode 100644 index 00000000..92a9602f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 6 | </PackageGroup> | ||
| 7 | <PayloadGroup Id="OrphanPayloads"> | ||
| 8 | <Payload Id="OrphanPayload" SourceFile="$(sys.SOURCEFILEPATH)" /> | ||
| 9 | </PayloadGroup> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs new file mode 100644 index 00000000..a00874ce --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 6 | </PackageGroup> | ||
| 7 | <Container Id="First"> | ||
| 8 | <PackageGroupRef Id="BundlePackages" /> | ||
| 9 | </Container> | ||
| 10 | <Container Id="Second"> | ||
| 11 | <PackageGroupRef Id="BundlePackages" /> | ||
| 12 | </Container> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs new file mode 100644 index 00000000..c717680b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" | ||
| 3 | xmlns:ex="http://www.example.com/scheams/v1/wxs"> | ||
| 4 | <Fragment> | ||
| 5 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 6 | <Component Id="MiscComponent" Guid="7C40C257-AB36-4B8C-8FD1-C56E0AC4AAEF"> | ||
| 7 | <RegistryKey> | ||
| 8 | <ex:Example /> | ||
| 9 | </RegistryKey> | ||
| 10 | </Component> | ||
| 11 | </ComponentGroup> | ||
| 12 | </Fragment> | ||
| 13 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs new file mode 100644 index 00000000..fc53c4a2 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage Id="Auto1" SourceFile="burn.exe" CacheId="Auto1" DetectCondition="none" /> | ||
| 6 | <ExePackage Id="Auto2" SourceFile="burn.exe" CacheId="Auto2" DetectCondition="none" /> | ||
| 7 | </PackageGroup> | ||
| 8 | <SetVariableRef Id="Dummy" /> | ||
| 9 | </Fragment> | ||
| 10 | <Fragment> | ||
| 11 | <SetVariable Id="Dummy" Variable="Dummy" /> | ||
| 12 | <PackageGroup Id="Unscheduled"> | ||
| 13 | <ExePackage Id="Unscheduled1" SourceFile="burn.exe" CacheId="Unscheduled1" DetectCondition="none" /> | ||
| 14 | </PackageGroup> | ||
| 15 | </Fragment> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs new file mode 100644 index 00000000..6cf8528e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage Id="Auto1" SourceFile="burn.exe" CacheId="Auto1" DetectCondition="none" /> | ||
| 6 | <ExePackage Id="Auto2" SourceFile="burn.exe" CacheId="Auto2" DetectCondition="none" /> | ||
| 7 | </PackageGroup> | ||
| 8 | <SetVariableRef Id="Dummy" /> | ||
| 9 | </Fragment> | ||
| 10 | <Fragment> | ||
| 11 | <SetVariable Id="Dummy" Variable="Dummy" /> | ||
| 12 | <PackageGroup Id="Unscheduled"> | ||
| 13 | <RollbackBoundary Id="Unscheduled1" /> | ||
| 14 | </PackageGroup> | ||
| 15 | </Fragment> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs new file mode 100644 index 00000000..c3528a67 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <Binary Id="Bound" SourceFile="!(wix.Test=data\test.txt)" /> | ||
| 5 | </Fragment> | ||
| 6 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt new file mode 100644 index 00000000..3b862323 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs new file mode 100644 index 00000000..5b41e807 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <BootstrapperApplication Id="fakeba"> | ||
| 4 | <BootstrapperApplicationDll SourceFile="$(sys.SOURCEFILEPATH)" DpiAwareness="gdiScaled" /> | ||
| 5 | </BootstrapperApplication> | ||
| 6 | </Fragment> | ||
| 7 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs new file mode 100644 index 00000000..7f5ea456 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage SourceFile="test.msi" CacheId="!(bind.packageDescription.test.msi)" /> | ||
| 6 | </PackageGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs new file mode 100644 index 00000000..e52302d4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 6 | </PackageGroup> | ||
| 7 | |||
| 8 | <BundleCustomData Id="BundleCustomTableBA"> | ||
| 9 | <BundleAttributeDefinition Id="Id" /> | ||
| 10 | <BundleAttributeDefinition Id="Column2" /> | ||
| 11 | |||
| 12 | <BundleElement> | ||
| 13 | <BundleAttribute Id="Id" Value="one" /> | ||
| 14 | <BundleAttribute Id="Column2" Value="two" /> | ||
| 15 | </BundleElement> | ||
| 16 | <BundleElement> | ||
| 17 | <BundleAttribute Id="Column2" Value="<" /> | ||
| 18 | <BundleAttribute Id="Id" Value=">" /> | ||
| 19 | </BundleElement> | ||
| 20 | </BundleCustomData> | ||
| 21 | |||
| 22 | <BundleCustomData Id="BundleCustomTableBE" ExtensionId="CustomTableExtension"> | ||
| 23 | <BundleAttributeDefinition Id="Id" /> | ||
| 24 | <BundleAttributeDefinition Id="Column2" /> | ||
| 25 | </BundleCustomData> | ||
| 26 | </Fragment> | ||
| 27 | |||
| 28 | <Fragment> | ||
| 29 | <BundleCustomDataRef Id="BundleCustomTableBA"> | ||
| 30 | <BundleElement> | ||
| 31 | <BundleAttribute Id="Id" Value="1" /> | ||
| 32 | <BundleAttribute Id="Column2" Value="2" /> | ||
| 33 | </BundleElement> | ||
| 34 | </BundleCustomDataRef> | ||
| 35 | |||
| 36 | <BundleCustomDataRef Id="BundleCustomTableBE"> | ||
| 37 | <BundleElement> | ||
| 38 | <BundleAttribute Id="Id" Value="one" /> | ||
| 39 | <BundleAttribute Id="Column2" Value="two" /> | ||
| 40 | </BundleElement> | ||
| 41 | <BundleElement> | ||
| 42 | <BundleAttribute Id="Column2" Value="<" /> | ||
| 43 | <BundleAttribute Id="Id" Value=">" /> | ||
| 44 | </BundleElement> | ||
| 45 | <BundleElement> | ||
| 46 | <BundleAttribute Id="Id" Value="1" /> | ||
| 47 | <BundleAttribute Id="Column2" Value="2" /> | ||
| 48 | </BundleElement> | ||
| 49 | </BundleCustomDataRef> | ||
| 50 | |||
| 51 | <BundleExtension Id="CustomTableExtension" SourceFile="fakeba.dll" Name="fakebext.dll" /> | ||
| 52 | </Fragment> | ||
| 53 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs new file mode 100644 index 00000000..eefae822 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <BundleExtension Id="ExampleBext" SourceFile="fakeba.dll" Name="fakebext.dll" /> | ||
| 5 | </Fragment> | ||
| 6 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs new file mode 100644 index 00000000..fd8d3698 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" | ||
| 3 | xmlns:ex="http://www.example.com/scheams/v1/wxs"> | ||
| 4 | <Fragment> | ||
| 5 | <ex:ExampleSearch Id="ExampleSearchBar" Variable="SearchBar" Condition="WixBundleInstalled" SearchFor="Bar" /> | ||
| 6 | <ex:ExampleSearch Id="ExampleSearchFoo" Variable="SearchFoo" SearchFor="Foo" /> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs new file mode 100644 index 00000000..c5a93eb3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" | ||
| 3 | xmlns:ex="http://www.example.com/scheams/v1/wxs"> | ||
| 4 | <Fragment> | ||
| 5 | <PackageGroup Id="BundlePackages"> | ||
| 6 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 7 | </PackageGroup> | ||
| 8 | |||
| 9 | <ex:ExampleSearchRef Id="ExampleSearchFoo" /> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs new file mode 100644 index 00000000..7303a05a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 6 | </PackageGroup> | ||
| 7 | |||
| 8 | <BundleExtensionRef Id="ExampleBext" /> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs new file mode 100644 index 00000000..f44fb7bc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" > | ||
| 2 | <Bundle Name="~TagTestBundle" Version="4.3.2.1" Manufacturer="Example Corporation" UpgradeCode="047730A5-30FE-4A62-A520-DA9381B8226A"> | ||
| 3 | <BootstrapperApplication> | ||
| 4 | <BootstrapperApplicationDll SourceFile="fakeba.dll" /> | ||
| 5 | </BootstrapperApplication> | ||
| 6 | |||
| 7 | <SoftwareTag Regid="wixtoolset.org" InstallPath="[ProgramFiles6432Folder]\Test\swidtag" /> | ||
| 8 | |||
| 9 | <Chain> | ||
| 10 | <MsiPackage SourceFile="test.msi"> | ||
| 11 | <MsiProperty Name="TEST" Value="1" /> | ||
| 12 | </MsiPackage> | ||
| 13 | </Chain> | ||
| 14 | </Bundle> | ||
| 15 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll new file mode 100644 index 00000000..64061ea0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll | |||
| @@ -0,0 +1 @@ | |||
| This is fakeba.dll. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs new file mode 100644 index 00000000..78e754c1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC"> | ||
| 3 | <ApprovedExeForElevation Id="TestExe" Key="WixToolset\BurnTesting" Value="Test" Bitness="always32" /> | ||
| 4 | </Bundle> | ||
| 5 | </Wix> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs new file mode 100644 index 00000000..18cdfd32 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Bundle Name="BurnBundle64" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC"> | ||
| 3 | <ApprovedExeForElevation Id="TestExe" Key="WixToolset\BurnTesting" Value="Test" Bitness="always64" /> | ||
| 4 | </Bundle> | ||
| 5 | </Wix> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs new file mode 100644 index 00000000..a93b23ef --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <PackageGroup Id="BundlePackages"> | ||
| 4 | <PackageGroupRef Id="MinimalPackageGroup"/> | ||
| 5 | </PackageGroup> | ||
| 6 | <Container Id="_1" Type="detached"> | ||
| 7 | <PackageGroupRef Id="MinimalPackageGroup"/> | ||
| 8 | </Container> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs new file mode 100644 index 00000000..e738b407 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC"> | ||
| 3 | <BootstrapperApplication> | ||
| 4 | <BootstrapperApplicationDll SourceFile="fakeba.dll" /> | ||
| 5 | </BootstrapperApplication> | ||
| 6 | <Chain> | ||
| 7 | <PackageGroupRef Id="BundlePackages" /> | ||
| 8 | </Chain> | ||
| 9 | </Bundle> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs new file mode 100644 index 00000000..b0bde4f6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="MinimalPackageGroup"> | ||
| 5 | <MsiPackage SourceFile="test.msi" /> | ||
| 6 | </PackageGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs new file mode 100644 index 00000000..514f9243 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{FE17A505-11A9-44D2-8D94-EB6BEAB8FF93}"> | ||
| 3 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 4 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="oekcr5lq"> | ||
| 5 | <Component Id="ProgIdComp" Guid="{5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8}" Bitness="always32"> | ||
| 6 | <Class Id="{F12A6F69-117F-471F-AE73-F8E74218F498}" Context="LocalServer32" Description="FakeClassF12A" Advertise="yes"> | ||
| 7 | <ProgId Id="73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D" Description="FakeClassF12A" Advertise="yes" /> | ||
| 8 | </Class> | ||
| 9 | <File Id="filTki4JQ2gSapF7wK4K1vd.4mDSFQ" Name="ProgIdComp.txt" KeyPath="yes" ShortName="bnvvntsc.txt" Source="MsiPackage\ProgIdComp.txt" /> | ||
| 10 | <RegistryValue Id="regUIIK326nDZpkWHuexeF58EikQvA" Root="HKCR" Key="73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D" Name="NoOpen" Value="NoOpen73E7" Type="string" /> | ||
| 11 | <RegistryValue Id="regY1F4E2lvu_Up6gV6c3jeN5ukn8s" Root="HKCR" Key="CLSID\{F12A6F69-117F-471F-AE73-F8E74218F498}\LocalServer32" Name="ThreadingModel" Value="Apartment" Type="string" /> | ||
| 12 | <RegistryValue Id="regvrhMurMp98anbQJkpgA8yJCefdM" Root="HKCR" Key="CLSID\{F12A6F69-117F-471F-AE73-F8E74218F498}\Version" Value="0.0.0.1" Type="string" /> | ||
| 13 | </Component> | ||
| 14 | </Directory> | ||
| 15 | </StandardDirectory> | ||
| 16 | <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle"> | ||
| 17 | <ComponentRef Id="ProgIdComp" Primary="yes" /> | ||
| 18 | </Feature> | ||
| 19 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 20 | <Media Id="1" /> | ||
| 21 | </Package> | ||
| 22 | </Wix> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs new file mode 100644 index 00000000..c0dc9bc0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="ClassComp" Directory="INSTALLFOLDER" Guid="9BFDA7DC-CA16-40B3-A6B5-961E60B30892"> | ||
| 6 | <File Source="test.txt" Name="ClassComp.txt"></File> | ||
| 7 | <Class Id="3FAED4CC-C473-4B8A-BE8B-303871377A4A" Advertise="yes" Context="LocalServer32" Description="FakeClass3FAE" ThreadingModel="apartment" Version="0.0.0.1" Icon="SampleIcon" IconIndex="0" /> | ||
| 8 | </Component> | ||
| 9 | </ComponentGroup> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi new file mode 100644 index 00000000..2cd10f09 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs new file mode 100644 index 00000000..15a9a0ce --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" | ||
| 3 | xmlns:ex="http://www.example.com/scheams/v1/wxs"> | ||
| 4 | <Fragment> | ||
| 5 | <ComponentGroup Id="OtherComponents" Directory="INSTALLFOLDER"> | ||
| 6 | <Component> | ||
| 7 | <File Source="other.txt" /> | ||
| 8 | <ex:Example Id="Other" Value="Value" /> | ||
| 9 | </Component> | ||
| 10 | </ComponentGroup> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs new file mode 100644 index 00000000..db07af2c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Property Id="ExampleProperty" Value="$(ex.Test)" /> | ||
| 8 | |||
| 9 | <PropertyRef Id="PropertyFromExampleWir" /> | ||
| 10 | |||
| 11 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 12 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 13 | <ComponentGroupRef Id="OtherComponents" /> | ||
| 14 | </Feature> | ||
| 15 | </Package> | ||
| 16 | |||
| 17 | <Fragment> | ||
| 18 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 19 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 20 | </StandardDirectory> | ||
| 21 | </Fragment> | ||
| 22 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs new file mode 100644 index 00000000..7f17b538 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" | ||
| 3 | xmlns:ex="http://www.example.com/scheams/v1/wxs"> | ||
| 4 | <Fragment> | ||
| 5 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 6 | <Component> | ||
| 7 | <File Source="example.txt" /> | ||
| 8 | <ex:Example Id="Foo" Value="Bar" /> | ||
| 9 | </Component> | ||
| 10 | </ComponentGroup> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt | |||
| @@ -0,0 +1 @@ | |||
| This is example.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt new file mode 100644 index 00000000..8c874ae7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt | |||
| @@ -0,0 +1 @@ | |||
| This is other.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs new file mode 100644 index 00000000..a0e921cb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component Guid="5917d193-7b5f-4d5d-bc2e-06aa210699cb"> | ||
| 6 | <RegistryValue Root="HKLM" Key="SOFTWARE\WixToolset" Name="Test" Value="test value" /> | ||
| 7 | </Component> | ||
| 8 | |||
| 9 | <Component Guid="5917d193-7b5f-4d5d-bc2e-06aa210699cb"> | ||
| 10 | <File Source="test.txt" /> | ||
| 11 | </Component> | ||
| 12 | </ComponentGroup> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs new file mode 100644 index 00000000..d7b5bdc0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 9 | </Feature> | ||
| 10 | </Package> | ||
| 11 | |||
| 12 | <Fragment> | ||
| 13 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 14 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 15 | </StandardDirectory> | ||
| 16 | </Fragment> | ||
| 17 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs new file mode 100644 index 00000000..beaf70bf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component Id="NullKeypathComponent" Guid="{C493379B-D655-4331-8F03-B618C70EA779}" KeyPath="yes"> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs new file mode 100644 index 00000000..ec757c5d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage Id="FirstX86"> | ||
| 6 | <PayloadGroupRef Id="FirstX86Payloads" /> | ||
| 7 | </MsiPackage> | ||
| 8 | <MsiPackage Id="FirstX64" Name="FirstX64\FirstX64.msi" SourceFile="FirstX64\" DownloadUrl="http://example.com/{0}/{1}/{2}" /> | ||
| 9 | </PackageGroup> | ||
| 10 | <Container Id="BundlePackages" Type="attached"> | ||
| 11 | <PackageGroupRef Id="BundlePackages" /> | ||
| 12 | </Container> | ||
| 13 | <PayloadGroup Id="FirstX86Payloads"> | ||
| 14 | <MsiPackagePayload Name="FirstX86\FirstX86.msi" SourceFile="FirstX86\" DownloadUrl="http://example.com/{0}/{1}/{2}" /> | ||
| 15 | </PayloadGroup> | ||
| 16 | </Fragment> | ||
| 17 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs new file mode 100644 index 00000000..e175a18f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage SourceFile="FirstX86.msi" /> | ||
| 6 | <PackageGroupRef Id="FirstX64" /> | ||
| 7 | </PackageGroup> | ||
| 8 | <PackageGroup Id="FirstX64"> | ||
| 9 | <MsiPackage SourceFile="FirstX64.msi" /> | ||
| 10 | </PackageGroup> | ||
| 11 | <Container Id="FirstX64" Name="FirstX64" Type="detached"> | ||
| 12 | <PackageGroupRef Id="FirstX64" /> | ||
| 13 | </Container> | ||
| 14 | </Fragment> | ||
| 15 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs new file mode 100644 index 00000000..0c5f8c7e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="{B5B23622-239B-4E3B-BDAB-67648CB975BF}"> | ||
| 4 | <BootstrapperApplication> | ||
| 5 | <BootstrapperApplicationDll SourceFile="fakeba.dll" /> | ||
| 6 | </BootstrapperApplication> | ||
| 7 | <Chain> | ||
| 8 | <PackageGroupRef Id="BundlePackages" /> | ||
| 9 | </Chain> | ||
| 10 | <PayloadGroupRef Id="Shared" /> | ||
| 11 | </Bundle> | ||
| 12 | <Fragment> | ||
| 13 | <PackageGroup Id="BundlePackages"> | ||
| 14 | <PackageGroupRef Id="FirstX64" /> | ||
| 15 | </PackageGroup> | ||
| 16 | <PackageGroup Id="FirstX64"> | ||
| 17 | <MsiPackage SourceFile="FirstX64.msi"> | ||
| 18 | <PayloadGroupRef Id="Shared" /> | ||
| 19 | </MsiPackage> | ||
| 20 | </PackageGroup> | ||
| 21 | <Container Id="FirstX64" Name="FirstX64" Type="detached"> | ||
| 22 | <PackageGroupRef Id="FirstX64" /> | ||
| 23 | </Container> | ||
| 24 | <PayloadGroup Id="Shared"> | ||
| 25 | <Payload Id="SharedPayload" SourceFile="$(sys.SOURCEFILEPATH)" /> | ||
| 26 | </PayloadGroup> | ||
| 27 | </Fragment> | ||
| 28 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs new file mode 100644 index 00000000..28900e55 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage SourceFile="FirstX86.msi" /> | ||
| 6 | <PackageGroupRef Id="FirstX64" /> | ||
| 7 | </PackageGroup> | ||
| 8 | <PackageGroup Id="FirstX64"> | ||
| 9 | <MsiPackage SourceFile="FirstX64.msi" /> | ||
| 10 | </PackageGroup> | ||
| 11 | <Container Id="FirstX64" Name="FirstX64" Type="attached"> | ||
| 12 | <PackageGroupRef Id="FirstX64" /> | ||
| 13 | </Container> | ||
| 14 | </Fragment> | ||
| 15 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs new file mode 100644 index 00000000..c7f549a3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <PackageGroupRef Id="FirstX86" /> | ||
| 6 | <PackageGroupRef Id="FirstX64" /> | ||
| 7 | </PackageGroup> | ||
| 8 | <PackageGroup Id="FirstX86"> | ||
| 9 | <MsiPackage SourceFile="FirstX86.msi"> | ||
| 10 | <PayloadGroupRef Id="Shared" /> | ||
| 11 | </MsiPackage> | ||
| 12 | </PackageGroup> | ||
| 13 | <PackageGroup Id="FirstX64"> | ||
| 14 | <MsiPackage SourceFile="FirstX64.msi"> | ||
| 15 | <PayloadGroupRef Id="Shared" /> | ||
| 16 | </MsiPackage> | ||
| 17 | </PackageGroup> | ||
| 18 | <Container Id="FirstX86" Name="FirstX86" Type="detached"> | ||
| 19 | <PackageGroupRef Id="FirstX86" /> | ||
| 20 | </Container> | ||
| 21 | <Container Id="FirstX64" Name="FirstX64" Type="detached"> | ||
| 22 | <PackageGroupRef Id="FirstX64" /> | ||
| 23 | </Container> | ||
| 24 | <PayloadGroup Id="Shared"> | ||
| 25 | <Payload Id="SharedPayload" SourceFile="$(sys.SOURCEFILEPATH)" /> | ||
| 26 | </PayloadGroup> | ||
| 27 | </Fragment> | ||
| 28 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs new file mode 100644 index 00000000..90d66cc3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="CopyFileComp" Directory="INSTALLFOLDER"> | ||
| 6 | <File Id="test.txt" Source="test.txt" /> | ||
| 7 | <CopyFile Id="MoveText" Delete="yes" SourceName="*.txt" DestinationDirectory="OtherFolder"/> | ||
| 8 | </Component> | ||
| 9 | </ComponentGroup> | ||
| 10 | </Fragment> | ||
| 11 | |||
| 12 | <Fragment> | ||
| 13 | <DirectoryRef Id="INSTALLFOLDER"> | ||
| 14 | <Directory Id="OtherFolder" Name="other" /> | ||
| 15 | </DirectoryRef> | ||
| 16 | </Fragment> | ||
| 17 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs new file mode 100644 index 00000000..be991c65 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <ComponentGroup Id="ProductComponents"> | ||
| 4 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 5 | </ComponentGroup> | ||
| 6 | |||
| 7 | <Binary Id="Binary1" SourceFile="test.txt" /> | ||
| 8 | <CustomAction Id="Action1" DllEntry="EntryPoint1" BinaryRef="Binary1" /> | ||
| 9 | <CustomAction Id="Action2" DllEntry="EntryPoint2" BinaryRef="Binary1" /> | ||
| 10 | <CustomAction Id="Action3" DllEntry="EntryPoint3" BinaryRef="Binary1" /> | ||
| 11 | |||
| 12 | <InstallExecuteSequence> | ||
| 13 | <Custom Action="Action1" After="Action2" /> | ||
| 14 | <Custom Action="Action2" After="Action3" /> | ||
| 15 | <Custom Action="Action3" After="Action1" /> | ||
| 16 | </InstallExecuteSequence> | ||
| 17 | </Fragment> | ||
| 18 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs new file mode 100644 index 00000000..c64ef143 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <ComponentGroup Id="ProductComponents"> | ||
| 4 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 5 | </ComponentGroup> | ||
| 6 | |||
| 7 | <Binary Id="Binary1" SourceFile="test.txt" /> | ||
| 8 | <CustomAction Id="Action1" DllEntry="EntryPoint1" BinaryRef="Binary1" /> | ||
| 9 | <CustomAction Id="Action2" DllEntry="EntryPoint2" BinaryRef="Binary1" /> | ||
| 10 | <CustomAction Id="Action3" DllEntry="EntryPoint3" BinaryRef="Binary1" /> | ||
| 11 | <CustomAction Id="Action4" DllEntry="EntryPoint4" BinaryRef="Binary1" /> | ||
| 12 | |||
| 13 | <InstallExecuteSequence> | ||
| 14 | <Custom Action="Action1" After="Action2" /> | ||
| 15 | <Custom Action="Action2" After="Action3" /> | ||
| 16 | <Custom Action="Action3" After="Action4" /> | ||
| 17 | <Custom Action="Action4" After="Action2" /> | ||
| 18 | </InstallExecuteSequence> | ||
| 19 | </Fragment> | ||
| 20 | </Wix> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs new file mode 100644 index 00000000..ff8741cf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <ComponentGroup Id="ProductComponents"> | ||
| 4 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 5 | </ComponentGroup> | ||
| 6 | |||
| 7 | <Binary Id="Binary1" SourceFile="test.txt" /> | ||
| 8 | <CustomAction Id="Action1" DllEntry="EntryPoint1" BinaryRef="Binary1" /> | ||
| 9 | |||
| 10 | <InstallExecuteSequence> | ||
| 11 | <Custom Action="Action1" After="InstallFiles" /> | ||
| 12 | </InstallExecuteSequence> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs new file mode 100644 index 00000000..f8ce1c38 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <ComponentGroup Id="ProductComponents"> | ||
| 4 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 5 | </ComponentGroup> | ||
| 6 | |||
| 7 | <Binary Id="Binary1" SourceFile="test.txt" /> | ||
| 8 | <CustomAction Id="CustomAction1" DllEntry="InvalidEntryPoint" BinaryRef="Binary1" /> | ||
| 9 | <CustomAction Id="DiscardOptimismAllBeingsWhoProceed" Error="Abandon hope all ye who enter here." /> | ||
| 10 | <CustomAction Id="CustomActionWithHiddenTarget" DllEntry="InvalidEntryPoint" Execute="deferred" HideTarget="yes" BinaryRef="Binary1" /> | ||
| 11 | <CustomAction Id="CustomAction2" Property="TestAdvtExecuteSequenceProperty" Value="1" /> | ||
| 12 | <AdminExecuteSequence> | ||
| 13 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 14 | </AdminExecuteSequence> | ||
| 15 | <AdminUISequence> | ||
| 16 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 17 | </AdminUISequence> | ||
| 18 | <AdvertiseExecuteSequence> | ||
| 19 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 20 | </AdvertiseExecuteSequence> | ||
| 21 | <InstallExecuteSequence> | ||
| 22 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 23 | </InstallExecuteSequence> | ||
| 24 | <InstallUISequence> | ||
| 25 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 26 | </InstallUISequence> | ||
| 27 | |||
| 28 | <UI> | ||
| 29 | <ProgressText Action="CustomAction2" Message="Progess2Text" /> | ||
| 30 | </UI> | ||
| 31 | </Fragment> | ||
| 32 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs new file mode 100644 index 00000000..10c4f91f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage DetectCondition="ForTestPurposesOnly" SourceFile="burn.exe" Permanent="yes" /> | ||
| 6 | <ExePackage Id="RemotePayloadExe" DetectCondition="ForTestPurposesOnly" Description="Override RemotePayload description" DisplayName="Override RemotePayload display name" Permanent="yes"> | ||
| 7 | <ExePackagePayload Description="RemotePayload description" Hash="a" ProductName="RemotePayload product name" Size="1" Version="1.0.0.0" Name="fake.exe" DownloadUrl="example.com" /> | ||
| 8 | </ExePackage> | ||
| 9 | <ExePackage DetectCondition="ForTestPurposesOnly" SourceFile="C:\Windows\system32\calc.exe" Permanent="yes" Description="Override harvested description" DisplayName="Override harvested display name" /> | ||
| 10 | </PackageGroup> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs new file mode 100644 index 00000000..d7d86008 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{83F9C623-26FE-42AB-951E-170022117F54}"> | ||
| 3 | <CustomTable Id="CustomTable1"> | ||
| 4 | <Column Id="Column1" PrimaryKey="yes" Type="string" Width="0" Category="text" Description="The first custom column." /> | ||
| 5 | <Column Id="Component_" Type="string" Width="72" KeyTable="Component" KeyColumn="1" Description="The custom table's Component reference" /> | ||
| 6 | <Row> | ||
| 7 | <Data Column="Column1" Value="Row1" /> | ||
| 8 | <Data Column="Component_" Value="test.txt" /> | ||
| 9 | </Row> | ||
| 10 | <Row> | ||
| 11 | <Data Column="Column1" Value="Row2" /> | ||
| 12 | <Data Column="Component_" Value="test.txt" /> | ||
| 13 | </Row> | ||
| 14 | </CustomTable> | ||
| 15 | <StandardDirectory Id="ProgramFiles6432Folder"> | ||
| 16 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="1egc1laj"> | ||
| 17 | <Component Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always32"> | ||
| 18 | <File Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Name="test.txt" KeyPath="yes" Source="MsiPackage\test.txt" /> | ||
| 19 | </Component> | ||
| 20 | </Directory> | ||
| 21 | </StandardDirectory> | ||
| 22 | <StandardDirectory Id="ProgramFilesFolder" /> | ||
| 23 | <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle"> | ||
| 24 | <ComponentRef Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" /> | ||
| 25 | </Feature> | ||
| 26 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 27 | <Media Id="1" /> | ||
| 28 | </Package> | ||
| 29 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs new file mode 100644 index 00000000..d32e808c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <CustomTable Id="CustomTable1"> | ||
| 9 | <Column Id="Column1" Type="string" PrimaryKey="yes" Category="text" Modularize="column" Description="The first custom column." /> | ||
| 10 | <Column Id="Component_" Type="string" Width="72" KeyTable="Component" KeyColumn="1" Description="The custom table's Component reference" Modularize="column" /> | ||
| 11 | <Row> | ||
| 12 | <Data Column="Column1" Value="Row1" /> | ||
| 13 | <Data Column="Component_" Value="test.txt" /> | ||
| 14 | </Row> | ||
| 15 | <Row> | ||
| 16 | <Data Column="Column1" Value="Row2" /> | ||
| 17 | <Data Column="Component_" Value="test.txt" /> | ||
| 18 | </Row> | ||
| 19 | </CustomTable> | ||
| 20 | |||
| 21 | <CustomTable Id="CustomTable2" Unreal="yes"> | ||
| 22 | <Column Id="ColumnA" Type="string" PrimaryKey="yes" /> | ||
| 23 | <Column Id="Component_" Type="string" Width="72" KeyTable="Component" KeyColumn="1" Modularize="column" /> | ||
| 24 | <Row> | ||
| 25 | <Data Column="ColumnA" Value="RowA" /> | ||
| 26 | <Data Column="Component_" Value="test.txt" /> | ||
| 27 | </Row> | ||
| 28 | <Row> | ||
| 29 | <Data Column="ColumnA" Value="RowB" /> | ||
| 30 | <Data Column="Component_" Value="test.txt" /> | ||
| 31 | </Row> | ||
| 32 | </CustomTable> | ||
| 33 | </Fragment> | ||
| 34 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs new file mode 100644 index 00000000..08a9c470 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <CustomTable Id="CustomTableWithFile"> | ||
| 9 | <Column Id="Column1" Type="string" PrimaryKey="yes" /> | ||
| 10 | <Column Id="Source" Type="binary" Width="0" /> | ||
| 11 | <Row> | ||
| 12 | <Data Column="Column1" Value="Row1" /> | ||
| 13 | <Data Column="Source" Value="file1.txt" /> | ||
| 14 | </Row> | ||
| 15 | <Row> | ||
| 16 | <Data Column="Source" Value="SourceDir\file2.txt" /> | ||
| 17 | <Data Column="Column1" Value="Row2" /> | ||
| 18 | </Row> | ||
| 19 | </CustomTable> | ||
| 20 | |||
| 21 | </Fragment> | ||
| 22 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl new file mode 100644 index 00000000..bc2ccf04 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 3 | |||
| 4 | <String Id="Loc1">This is row one</String> | ||
| 5 | <String Id="Loc2">This is row two</String> | ||
| 6 | |||
| 7 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs new file mode 100644 index 00000000..e1da74f8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <CustomTable Id="CustomTableLocalized"> | ||
| 9 | <Column Id="Column1" Type="string" PrimaryKey="yes" /> | ||
| 10 | <Column Id="DataColumn" Type="string" Localizable="yes" Width="255" /> | ||
| 11 | <Row> | ||
| 12 | <Data Column="Column1" Value="Row1" /> | ||
| 13 | <Data Column="DataColumn" Value="!(loc.Loc1)" /> | ||
| 14 | </Row> | ||
| 15 | <Row> | ||
| 16 | <Data Column="Column1" Value="Row2" /> | ||
| 17 | <Data Column="DataColumn" Value="!(loc.Loc2)" /> | ||
| 18 | </Row> | ||
| 19 | </CustomTable> | ||
| 20 | </Fragment> | ||
| 21 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt new file mode 100644 index 00000000..97f701ce --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt | |||
| @@ -0,0 +1 @@ | |||
| This is file1.txt \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt new file mode 100644 index 00000000..46493186 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt | |||
| @@ -0,0 +1 @@ | |||
| This is file2.txt \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs new file mode 100644 index 00000000..71553e2a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Codepage="65001" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{047730A5-30FE-4A62-A520-DA9381B8226A}" Version="1.0.0.0" Compressed="yes" InstallerVersion="200" ProductCode="{6F9B5694-F0F1-437C-919B-0D2DAF2D9DEA}"> | ||
| 3 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 4 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="oekcr5lq"> | ||
| 5 | <Component Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Guid="" Bitness="always32"> | ||
| 6 | <File Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Name="test.txt" KeyPath="yes" Source="SourceDir\File\filcV1yrx0x8wJWj4qMzcH21jwkPko" /> | ||
| 7 | </Component> | ||
| 8 | </Directory> | ||
| 9 | </StandardDirectory> | ||
| 10 | <Feature Id="ProductFeature" Level="1" Title="MsiPackage"> | ||
| 11 | <ComponentRef Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" /> | ||
| 12 | </Feature> | ||
| 13 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 14 | <Media Id="1" Cabinet="example.cab" /> | ||
| 15 | </Package> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab new file mode 100644 index 00000000..125eeb2c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi new file mode 100644 index 00000000..81335041 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs new file mode 100644 index 00000000..246bcafc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Codepage="65001" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{047730A5-30FE-4A62-A520-DA9381B8226A}" Version="1.0.0.0" Compressed="yes" InstallerVersion="200" ProductCode="{6F9B5694-F0F1-437C-919B-0D2DAF2D9DEA}"> | ||
| 3 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 4 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="oekcr5lq"> | ||
| 5 | <Component Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always32"> | ||
| 6 | <File Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Name="test.txt" KeyPath="yes" Source="SourceDir\File\filcV1yrx0x8wJWj4qMzcH21jwkPko" /> | ||
| 7 | </Component> | ||
| 8 | </Directory> | ||
| 9 | </StandardDirectory> | ||
| 10 | <Feature Id="ProductFeature" Level="1" Title="MsiPackage"> | ||
| 11 | <ComponentRef Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" /> | ||
| 12 | </Feature> | ||
| 13 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 14 | <Media Id="1" Cabinet="example.cab" /> | ||
| 15 | </Package> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab new file mode 100644 index 00000000..125eeb2c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi new file mode 100644 index 00000000..9cb6d6bc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs new file mode 100644 index 00000000..81915759 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Codepage="65001" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{047730A5-30FE-4A62-A520-DA9381B8226A}" Version="1.0.0.0" Compressed="yes" ProductCode="{C51B773A-B3BE-4F29-A8A9-549AAF7FF6EC}"> | ||
| 3 | <StandardDirectory Id="ProgramFiles64Folder"> | ||
| 4 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="oekcr5lq"> | ||
| 5 | <Component Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always64"> | ||
| 6 | <File Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Name="test.txt" KeyPath="yes" Source="SourceDir\File\filcV1yrx0x8wJWj4qMzcH21jwkPko" /> | ||
| 7 | </Component> | ||
| 8 | </Directory> | ||
| 9 | </StandardDirectory> | ||
| 10 | <Feature Id="ProductFeature" Level="1" Title="MsiPackage"> | ||
| 11 | <ComponentRef Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" /> | ||
| 12 | </Feature> | ||
| 13 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 14 | <Media Id="1" Cabinet="example.cab" /> | ||
| 15 | </Package> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab new file mode 100644 index 00000000..125eeb2c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi new file mode 100644 index 00000000..762b136c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs new file mode 100644 index 00000000..7c5fe3cf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Module Id="MergeModule1" Language="1033" Version="1.0.0.0" InstallerVersion="200"> | ||
| 4 | <Directory Id="MergeRedirectFolder"> | ||
| 5 | <Component Id="ModuleComponent2" Guid="{BB222EE8-229B-4051-9443-49E348F0CC77}" Bitness="always32"> | ||
| 6 | <File Id="File2" ShortName="sfmxqeab.wxs" Name="MergeModule.wxs" KeyPath="yes" Source="SourceDir\File\File2.F844F0E3_8CB4_4A0F_973E_31C4F9338382" /> | ||
| 7 | </Component> | ||
| 8 | </Directory> | ||
| 9 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 10 | <Directory Id="WixTestDir" ShortName="7bhhvaai" Name="WiX Toolset Test Directory"> | ||
| 11 | <Component Id="ModuleComponent1" Guid="{D86EC5A2-9576-4699-BDC3-00586FF72CBE}" Bitness="always32"> | ||
| 12 | <File Id="File1" ShortName="gahushls.wxs" Name="MergeModule.wxs" KeyPath="yes" Source="SourceDir\File\File1.F844F0E3_8CB4_4A0F_973E_31C4F9338382" /> | ||
| 13 | </Component> | ||
| 14 | </Directory> | ||
| 15 | </StandardDirectory> | ||
| 16 | <SummaryInformation Description="MergeModule1" Manufacturer="WiX Toolset contributors" /> | ||
| 17 | </Module> | ||
| 18 | </Wix> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm new file mode 100644 index 00000000..2a7b5e3a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs new file mode 100644 index 00000000..2f277956 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | |||
| 10 | <DirectoryRef Id="INSTALLFOLDER"> | ||
| 11 | <Directory Id="DUPLICATENAMEANDSHORTNAME" Name="duplicat" ShortName="duplicat"></Directory> | ||
| 12 | <Directory Id="NAMEWITHSHORTVALUE" Name="SHORTVAL"></Directory> | ||
| 13 | <Directory Id="NAMEANDSHORTNAME" Name="NameAndShortName" ShortName="SHORTNAM"></Directory> | ||
| 14 | <Directory Id="SHORTNAMEONLY" Name="SHORTONL"></Directory> | ||
| 15 | <Directory Id="SOURCENAME" Name="NameAndSourceName" SourceName="SourceNameWithName"></Directory> | ||
| 16 | <Directory Id="SOURCENAMESONLY" SourceName="SourceNameOnly" ShortSourceName="SRCNAMON"></Directory> | ||
| 17 | <Directory Id="NAMEANDSHORTSOURCENAME" Name="NameAndShortSourceName" ShortName="NAMEASSN"></Directory> | ||
| 18 | <Directory Id="SHORTNAMEANDLONGSOURCENAME" SourceName="ShortNameAndLongSourceName" Name="SHNALSNM"></Directory> | ||
| 19 | <Directory Id="SOURCENAMEWITHSHORTVALUE" SourceName="SRTSRCVL"></Directory> | ||
| 20 | <Directory Id="Folder1" Name="Folder.1"></Directory> | ||
| 21 | <Directory Id="Folder12" Name="Folder.12"></Directory> | ||
| 22 | <Directory Id="Folder123" Name="Folder.123"></Directory> | ||
| 23 | <Directory Id="Folder1234" Name="Folder.1234"></Directory> | ||
| 24 | </DirectoryRef> | ||
| 25 | </Fragment> | ||
| 26 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs new file mode 100644 index 00000000..6df8a7c0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC" ProviderKey="MyProviderKey,v1.0"> | ||
| 3 | <BootstrapperApplication> | ||
| 4 | <BootstrapperApplicationDll SourceFile="fakeba.dll" /> | ||
| 5 | </BootstrapperApplication> | ||
| 6 | <Chain> | ||
| 7 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 8 | </Chain> | ||
| 9 | </Bundle> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs new file mode 100644 index 00000000..4d188d3a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage DetectCondition="DetectedSomething" SourceFile="burn.exe"> | ||
| 6 | <Provides Key="DependencyTests_ExeA,v1.0" Version="1.0.0.0" /> | ||
| 7 | </ExePackage> | ||
| 8 | </PackageGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs new file mode 100644 index 00000000..9c3a9690 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage SourceFile="UsingProvides.msi" /> | ||
| 6 | </PackageGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs new file mode 100644 index 00000000..ec6e62df --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <UI Id="CustomDialog"> | ||
| 4 | <Dialog Id="FirstDialog" Width="100" Height="100"> | ||
| 5 | <Control Id="Title" Type="Text" X="0" Y="0" Width="90" Height="13" TabSkip="no" Text="FirstDialogTitle" /> | ||
| 6 | <Control Id="Header" Type="Text" X="0" Y="13" Width="90" Height="13" TabSkip="no" Text="FirstDialogHeader" HideCondition="Installed" DisableCondition="Installed" /> | ||
| 7 | </Dialog> | ||
| 8 | <Dialog Id="SecondDialog" Width="100" Height="100"> | ||
| 9 | <Control Id="Title" Type="Text" X="0" Y="0" Width="90" Height="13" TabSkip="no" Text="SecondDialogTitle" /> | ||
| 10 | <Control Id="OptionalCheckBox" Type="CheckBox" X="0" Y="13" Width="100" Height="40" Hidden="yes" Property="WIXUI_EXITDIALOGOPTIONALCHECKBOX" CheckBoxValue="1" Text="[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]" ToolTip="Optional checkbox" Help="Check this box for fun" ShowCondition="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT AND NOT Installed" /> | ||
| 11 | </Dialog> | ||
| 12 | |||
| 13 | <InstallUISequence> | ||
| 14 | <Show Dialog="SecondDialog" Before="FirstDialog" Overridable="yes" Condition="NOT Installed" /> | ||
| 15 | </InstallUISequence> | ||
| 16 | </UI> | ||
| 17 | </Fragment> | ||
| 18 | <Fragment> | ||
| 19 | <UI Id="CustomUI"> | ||
| 20 | <DialogRef Id="FirstDialog" /> | ||
| 21 | <DialogRef Id="SecondDialog" /> | ||
| 22 | |||
| 23 | <Publish Dialog="FirstDialog" Control="Next" Event="NewDialog" Value="SecondDialog" Condition="Installed AND PATCH" /> | ||
| 24 | |||
| 25 | <InstallUISequence> | ||
| 26 | <Show Dialog="FirstDialog" Before="SecondDialog" Condition="Installed AND PATCH" /> | ||
| 27 | <Show Dialog="SecondDialog" Before="ExecuteAction" Condition="NOT Installed" /> | ||
| 28 | </InstallUISequence> | ||
| 29 | </UI> | ||
| 30 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 31 | <Component> | ||
| 32 | <File Source="test.txt" /> | ||
| 33 | </Component> | ||
| 34 | </ComponentGroup> | ||
| 35 | </Fragment> | ||
| 36 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs new file mode 100644 index 00000000..3e7887c4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="BinFolder" /> | ||
| 5 | </Fragment> | ||
| 6 | <Fragment> | ||
| 7 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 8 | <Directory Id="CompanyFolder" Name="!(bind.Property.Manufacturer)"> | ||
| 9 | <Directory Id="BinFolder" Name="." /> | ||
| 10 | </Directory> | ||
| 11 | </StandardDirectory> | ||
| 12 | </Fragment> | ||
| 13 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs new file mode 100644 index 00000000..6e9a4495 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="BinFolder" /> | ||
| 5 | </Fragment> | ||
| 6 | <Fragment> | ||
| 7 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 8 | <Directory Id="BinFolder" Name="bin" SourceName="bin" /> | ||
| 9 | </StandardDirectory> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs new file mode 100644 index 00000000..50cf6850 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER" /> | ||
| 5 | </Fragment> | ||
| 6 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs new file mode 100644 index 00000000..cc87b49f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="BinFolder" /> | ||
| 5 | </Fragment> | ||
| 6 | <Fragment> | ||
| 7 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 8 | <Directory Id="BinFolder" Name="Example Corporation\Test Product\bin" /> | ||
| 9 | </StandardDirectory> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs new file mode 100644 index 00000000..a58b68c8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="GroupA" /> | ||
| 6 | <ComponentGroupRef Id="GroupB" /> | ||
| 7 | </ComponentGroup> | ||
| 8 | </Fragment> | ||
| 9 | |||
| 10 | <Fragment> | ||
| 11 | <ComponentGroup Id="GroupA" Directory="INSTALLFOLDER" Subdirectory="dupe"> | ||
| 12 | <Component> | ||
| 13 | <File Name="a.txt" Source="test.txt" /> | ||
| 14 | </Component> | ||
| 15 | </ComponentGroup> | ||
| 16 | </Fragment> | ||
| 17 | |||
| 18 | <Fragment> | ||
| 19 | <ComponentGroup Id="GroupB" Directory="INSTALLFOLDER" Subdirectory="dupe"> | ||
| 20 | <Component> | ||
| 21 | <File Name="b.txt" Source="test.txt" /> | ||
| 22 | </Component> | ||
| 23 | </ComponentGroup> | ||
| 24 | </Fragment> | ||
| 25 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs new file mode 100644 index 00000000..01767abb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <EnsureTable Id="Wix4Example" /> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs new file mode 100644 index 00000000..de9744a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="WixEnvironmentTest" Guid="{068C1CF4-DA54-4221-B0D2-E7310770DF0B}" Directory="INSTALLFOLDER"> | ||
| 6 | <Environment Id="WixEnvironmentTest1" Action="set" Name="WixEnvTest1"/> | ||
| 7 | <Environment Id="WixEnvironmentTest2" Action="create" Name="WixEnvTest1"/> | ||
| 8 | <Environment Id="WixEnvironmentTest3" Action="remove" Name="WixEnvTest1"/> | ||
| 9 | <Environment Id="WixEnvironmentTest4" Name="WIX" Action="set" System="yes" Value="[INSTALLFOLDER]" /> | ||
| 10 | <Environment Id="PATH" Name="PATH" Action="set" Part="first" Value="[INSTALLFOLDER]; " System="yes" /> | ||
| 11 | </Component> | ||
| 12 | </ComponentGroup> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl new file mode 100644 index 00000000..066e16bb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 8 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 9 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs new file mode 100644 index 00000000..287085e8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <CustomAction Id="CanWeReferenceAnError_YesWeCan" Error="1234" /> | ||
| 8 | <CustomAction Id="TextErrorsWorkOKToo" Error="If you see this, something went wrong." /> | ||
| 9 | |||
| 10 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 11 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 12 | </Feature> | ||
| 13 | </Package> | ||
| 14 | |||
| 15 | <Fragment> | ||
| 16 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 17 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 18 | </StandardDirectory> | ||
| 19 | </Fragment> | ||
| 20 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs new file mode 100644 index 00000000..88a4ac81 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <UI> | ||
| 5 | <Error Id="1234" Message="Category 55 Emergency Doomsday Crisis" /> | ||
| 6 | <Error Id="5678" Message=" " /> | ||
| 7 | </UI> | ||
| 8 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 9 | <Component> | ||
| 10 | <File Source="test.txt" /> | ||
| 11 | </Component> | ||
| 12 | </ComponentGroup> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs new file mode 100644 index 00000000..5c84f33e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Property Id="ExampleProperty" Value="$(ex.Test)" /> | ||
| 8 | |||
| 9 | <PropertyRef Id="PropertyFromExampleWir" /> | ||
| 10 | |||
| 11 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 12 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 13 | </Feature> | ||
| 14 | </Package> | ||
| 15 | |||
| 16 | <Fragment> | ||
| 17 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 18 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 19 | </StandardDirectory> | ||
| 20 | </Fragment> | ||
| 21 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs new file mode 100644 index 00000000..7f17b538 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" | ||
| 3 | xmlns:ex="http://www.example.com/scheams/v1/wxs"> | ||
| 4 | <Fragment> | ||
| 5 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 6 | <Component> | ||
| 7 | <File Source="example.txt" /> | ||
| 8 | <ex:Example Id="Foo" Value="Bar" /> | ||
| 9 | </Component> | ||
| 10 | </ComponentGroup> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt | |||
| @@ -0,0 +1 @@ | |||
| This is example.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs new file mode 100644 index 00000000..e57180f7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="TestPackageGroup"> | ||
| 5 | <ExePackage InstallArguments="-install" | ||
| 6 | SourceFile="testsetup.exe" /> | ||
| 7 | </PackageGroup> | ||
| 8 | </Fragment> | ||
| 9 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs new file mode 100644 index 00000000..0b094860 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="TestPackageGroup"> | ||
| 5 | <ExePackage DetectCondition="" | ||
| 6 | InstallArguments="-install" | ||
| 7 | UninstallArguments="-uninstall" | ||
| 8 | SourceFile="testsetup.exe" /> | ||
| 9 | </PackageGroup> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs new file mode 100644 index 00000000..be302720 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <FeatureGroup Id="ProductFeatureGroup"> | ||
| 9 | <Feature Id="ParentFeature" Title="ParentFeatureTitle"> | ||
| 10 | <Feature Id="ChildFeature" Title="ChildFeatureTitle"></Feature> | ||
| 11 | </Feature> | ||
| 12 | </FeatureGroup> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs new file mode 100644 index 00000000..6fb9ef05 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="FontComp" Directory="INSTALLFOLDER"> | ||
| 6 | <File Id="test.txt" Source="test.txt" FontTitle="FakeFont" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs new file mode 100644 index 00000000..6ac48963 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="TrueTypeFontComp" Directory="INSTALLFOLDER" Guid="C8116E5E-F731-427C-AF43-6FF8B8D7DA64"> | ||
| 6 | <File Id="TrueTypeFontFile" Source="test.txt" Name="TrueTypeFontComp.ttf" TrueType="yes" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs new file mode 100644 index 00000000..8fff563e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Language="1033" Version="1.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <ComponentGroupRef Id="ProductComponents.x86" /> | ||
| 9 | <ComponentGroupRef Id="ProductComponents.x64" /> | ||
| 10 | <ComponentGroupRef Id="ProductComponents.arm" /> | ||
| 11 | </Feature> | ||
| 12 | </Package> | ||
| 13 | |||
| 14 | <Fragment> | ||
| 15 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 16 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 17 | </StandardDirectory> | ||
| 18 | </Fragment> | ||
| 19 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs new file mode 100644 index 00000000..2a75e3d7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <?foreach ComponentPlatform in x86;x64;arm ?> | ||
| 4 | <Fragment> | ||
| 5 | <ComponentGroup Id="ProductComponents.$(var.ComponentPlatform)" Directory="INSTALLFOLDER"> | ||
| 6 | <Component> | ||
| 7 | <File Name="$(var.ComponentPlatform).dll" Source="test.txt" /> | ||
| 8 | </Component> | ||
| 9 | </ComponentGroup> | ||
| 10 | </Fragment> | ||
| 11 | <?endforeach?> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs new file mode 100644 index 00000000..1de84e81 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <Icon Id="SampleIcon" SourceFile="burn.exe" /> | ||
| 5 | </Fragment> | ||
| 6 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs new file mode 100644 index 00000000..0bd80c50 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <?include Package.wxi ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Package Name="MsiPackage" Language="1033" Version="$(var.ProductVersion)" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 4 | |||
| 5 | |||
| 6 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 7 | |||
| 8 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 9 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 10 | </Feature> | ||
| 11 | </Package> | ||
| 12 | |||
| 13 | <Fragment> | ||
| 14 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 15 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 16 | </StandardDirectory> | ||
| 17 | </Fragment> | ||
| 18 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs new file mode 100644 index 00000000..7a0485ed --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <?include DontDoThis.wxi ?> | ||
| 6 | </ComponentGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi new file mode 100644 index 00000000..03885e3e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Include xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Component> | ||
| 4 | <File Source="test.txt" /> | ||
| 5 | </Component> | ||
| 6 | </Include> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi new file mode 100644 index 00000000..f2df3b86 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Include xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <?define ProductVersion = "1.2.3" ?> | ||
| 4 | </Include> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs new file mode 100644 index 00000000..7826d673 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Property Id="INSTANCEPROPERTY" Secure="yes" /> | ||
| 8 | |||
| 9 | <InstanceTransforms Property="INSTANCEPROPERTY"> | ||
| 10 | <Instance Id="I1" ProductCode="*" ProductName="MsiPackage (Instance 1)" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" /> | ||
| 11 | </InstanceTransforms> | ||
| 12 | |||
| 13 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 14 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 15 | </Feature> | ||
| 16 | </Package> | ||
| 17 | |||
| 18 | <Fragment> | ||
| 19 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 20 | <Directory Id="INSTALLFOLDER" Name="MsiPackageInstance" /> | ||
| 21 | </StandardDirectory> | ||
| 22 | </Fragment> | ||
| 23 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl new file mode 100644 index 00000000..f7453566 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 3 | |||
| 4 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 5 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 6 | |||
| 7 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl new file mode 100644 index 00000000..ef287da7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="ja-JP"> | ||
| 3 | |||
| 4 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 5 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 6 | |||
| 7 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl new file mode 100644 index 00000000..10ebf2c5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl"> | ||
| 3 | |||
| 4 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 5 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 6 | |||
| 7 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs new file mode 100644 index 00000000..13c79e90 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="~DefaultLanguagePackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a"> | ||
| 3 | |||
| 4 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 5 | |||
| 6 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 7 | <Component Directory="INSTALLFOLDER"> | ||
| 8 | <File Source="test.txt" /> | ||
| 9 | </Component> | ||
| 10 | </Feature> | ||
| 11 | </Package> | ||
| 12 | |||
| 13 | <Fragment> | ||
| 14 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 15 | <Directory Id="INSTALLFOLDER" Name="Example Corporation\MsiPackage" /> | ||
| 16 | </StandardDirectory> | ||
| 17 | </Fragment> | ||
| 18 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl new file mode 100644 index 00000000..596ee077 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" SummaryInformationCodepage="1252" Culture="ja-JP"> | ||
| 3 | |||
| 4 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 5 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 6 | |||
| 7 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs new file mode 100644 index 00000000..dfae2157 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | <Component Id="MiscComponent" Guid="D1414BA5-F8DE-4979-938D-C8D0F61A62C9" Directory="INSTALLFOLDER"> | ||
| 7 | <CreateFolder> | ||
| 8 | <Permission User="Administrator"></Permission> | ||
| 9 | </CreateFolder> | ||
| 10 | </Component> | ||
| 11 | </ComponentGroup> | ||
| 12 | </Fragment> | ||
| 13 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs new file mode 100644 index 00000000..4fd3493a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <Upgrade Id="01120000-00E0-0000-0000-0000000FF1CE"> | ||
| 6 | <UpgradeVersion ExcludeLanguages="no" IgnoreRemoveFailure="yes" IncludeMaximum="no" IncludeMinimum="yes" Maximum="13.0.0" Minimum="12.0.0" OnlyDetect="no" Property="BLAHBLAHBLAH" /> | ||
| 7 | </Upgrade> | ||
| 8 | <!--<Property Id="BLAHBLAHBLAH" Secure="yes" />--> | ||
| 9 | |||
| 10 | <InstallExecuteSequence> | ||
| 11 | <RemoveExistingProducts After="InstallValidate" /> | ||
| 12 | </InstallExecuteSequence> | ||
| 13 | |||
| 14 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 15 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 16 | </Feature> | ||
| 17 | </Package> | ||
| 18 | |||
| 19 | <Fragment> | ||
| 20 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 21 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 22 | </StandardDirectory> | ||
| 23 | </Fragment> | ||
| 24 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs new file mode 100644 index 00000000..e7492db4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="~MultiMedia" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" | ||
| 3 | UpgradeCode="12E4699F-E774-4D05-8A01-5BDD41BBA127"> | ||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 6 | |||
| 7 | <Media Id="1" Cabinet="cab1.cab" /> | ||
| 8 | <Media Id="2" Cabinet="cab2.cab" /> | ||
| 9 | |||
| 10 | <Feature Id="ProductFeature" Title="MsiPackageTitle"> | ||
| 11 | <Component Directory="ProgramFilesFolder" Subdirectory="~MultiMedia" DiskId="1"> | ||
| 12 | <File Source="a1.txt" /> | ||
| 13 | </Component> | ||
| 14 | |||
| 15 | <Component Directory="ProgramFilesFolder" Subdirectory="~MultiMedia" DiskId="1"> | ||
| 16 | <File Source="a2.txt" /> | ||
| 17 | </Component> | ||
| 18 | |||
| 19 | <Component Directory="ProgramFilesFolder" Subdirectory="~MultiMedia" DiskId="2"> | ||
| 20 | <File Source="b2.txt" /> | ||
| 21 | </Component> | ||
| 22 | |||
| 23 | <Component Directory="ProgramFilesFolder" Subdirectory="~MultiMedia" DiskId="2"> | ||
| 24 | <File Source="b1.txt" /> | ||
| 25 | </Component> | ||
| 26 | </Feature> | ||
| 27 | </Package> | ||
| 28 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt new file mode 100644 index 00000000..ad9cdcb5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt | |||
| @@ -0,0 +1 @@ | |||
| This is a1.txt \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt new file mode 100644 index 00000000..d5de23de --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt | |||
| @@ -0,0 +1 @@ | |||
| This is a2.txt \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt new file mode 100644 index 00000000..88bc4a56 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt | |||
| @@ -0,0 +1 @@ | |||
| This is b1.txt \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt new file mode 100644 index 00000000..38525276 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt | |||
| @@ -0,0 +1 @@ | |||
| This is b2.txt \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 6 | </ComponentGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 6 | </ComponentGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 6 | </ComponentGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup" /> | ||
| 6 | </ComponentGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs new file mode 100644 index 00000000..e6527a36 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage SourceFile="FirstX64\" Name="FirstX64\FirstX64.msi" /> | ||
| 6 | <RollbackBoundary Transaction="yes" /> | ||
| 7 | <MsiPackage SourceFile="FirstX86\" Name="FirstX86\FirstX86.msi" /> | ||
| 8 | <MsiPackage SourceFile="SecondX86\" Name="SecondX86\SecondX86.msi" /> | ||
| 9 | <MsiPackage SourceFile="SecondX64\" Name="SecondX64\SecondX64.msi" /> | ||
| 10 | </PackageGroup> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs new file mode 100644 index 00000000..f1c939db --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage SourceFile="FirstX86\" Name="FirstX86\FirstX86.msi" /> | ||
| 6 | <RollbackBoundary Transaction="yes" /> | ||
| 7 | <MsiPackage SourceFile="FirstX64\" Name="FirstX64\FirstX64.msi" /> | ||
| 8 | <MsiPackage SourceFile="SecondX64\" Name="SecondX64\SecondX64.msi" /> | ||
| 9 | <MsiPackage SourceFile="SecondX86\" Name="SecondX86\SecondX86.msi" /> | ||
| 10 | </PackageGroup> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs new file mode 100644 index 00000000..dbca3393 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC"> | ||
| 3 | <BootstrapperApplication> | ||
| 4 | <BootstrapperApplicationDll SourceFile="fakeba.dll" /> | ||
| 5 | </BootstrapperApplication> | ||
| 6 | |||
| 7 | <Chain> | ||
| 8 | <MsuPackage DetectCondition="DetectedTheMsu" SourceFile="test.msu" /> | ||
| 9 | </Chain> | ||
| 10 | </Bundle> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll new file mode 100644 index 00000000..b3cf17d8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll | |||
| @@ -0,0 +1 @@ | |||
| This is a fake BA DLL | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu new file mode 100644 index 00000000..d63da4be --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu | |||
| @@ -0,0 +1 @@ | |||
| This is a fake MSU package | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs new file mode 100644 index 00000000..2b1a1a0f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="yes" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <?ifndef MediaTemplateCompressionLevel?> | ||
| 8 | <Media Id="1" Cabinet="example1.cab" /> | ||
| 9 | <Media Id="2" Cabinet="example2.cab" /> | ||
| 10 | <?elseif $(MediaTemplateCompressionLevel) = ""?> | ||
| 11 | <MediaTemplate /> | ||
| 12 | <?else?> | ||
| 13 | <MediaTemplate CabinetTemplate="lowcab{0}.cab" CompressionLevel="$(MediaTemplateCompressionLevel)" /> | ||
| 14 | <?endif?> | ||
| 15 | |||
| 16 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 17 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 18 | </Feature> | ||
| 19 | </Package> | ||
| 20 | |||
| 21 | <Fragment> | ||
| 22 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 23 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 24 | </StandardDirectory> | ||
| 25 | </Fragment> | ||
| 26 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs new file mode 100644 index 00000000..82797ebe --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="$(env.WINDIR)\Notepad.exe" /> | ||
| 7 | </Component> | ||
| 8 | <Component> | ||
| 9 | <File Source="test.txt" /> | ||
| 10 | </Component> | ||
| 11 | </ComponentGroup> | ||
| 12 | </Fragment> | ||
| 13 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs new file mode 100644 index 00000000..0bf0e963 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <InstallExecuteSequence> | ||
| 8 | <ValidateProductID Suppress="yes" /> | ||
| 9 | </InstallExecuteSequence> | ||
| 10 | |||
| 11 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 12 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 13 | <ComponentGroupRef Id="Foo1" /> | ||
| 14 | <ComponentGroupRef Id="Foo2" /> | ||
| 15 | </Feature> | ||
| 16 | |||
| 17 | <!--<CustomActionRef Id="SetFoo" />--> | ||
| 18 | |||
| 19 | </Package> | ||
| 20 | |||
| 21 | <Fragment Id="SetFoo"> | ||
| 22 | <CustomAction Id="SetFoo" Property="FOO" Value="BOB" /> | ||
| 23 | <CustomAction Id="SetBar" Property="BAR" Value="BOB" /> | ||
| 24 | </Fragment> | ||
| 25 | |||
| 26 | <Fragment Id="Foo1"> | ||
| 27 | <ComponentGroup Id="Foo1" /> | ||
| 28 | |||
| 29 | <InstallExecuteSequence> | ||
| 30 | <Custom Action="SetFoo" Before="SetBar" /> | ||
| 31 | <Custom Action="SetBar" Overridable="yes" Before="AppSearch" /> | ||
| 32 | </InstallExecuteSequence> | ||
| 33 | </Fragment> | ||
| 34 | |||
| 35 | <Fragment Id="Foo2"> | ||
| 36 | <ComponentGroup Id="Foo2" /> | ||
| 37 | |||
| 38 | <InstallExecuteSequence> | ||
| 39 | <Custom Action="SetBar" Before="AppSearch" /> | ||
| 40 | </InstallExecuteSequence> | ||
| 41 | </Fragment> | ||
| 42 | |||
| 43 | <Fragment Id="Directories"> | ||
| 44 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 45 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 46 | </StandardDirectory> | ||
| 47 | </Fragment> | ||
| 48 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs new file mode 100644 index 00000000..5e1b99ff --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsuPackage Id="MissingSourceFileAndHash" Permanent="yes" DetectCondition="none"> | ||
| 6 | <MsuPackagePayload DownloadUrl="example.com" /> | ||
| 7 | </MsuPackage> | ||
| 8 | </PackageGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs new file mode 100644 index 00000000..f220d81a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage Id="MissingSourceFileAndName"> | ||
| 6 | <MsiPackagePayload DownloadUrl="example.com" /> | ||
| 7 | </MsiPackage> | ||
| 8 | </PackageGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs new file mode 100644 index 00000000..149870a4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage Id="PackagePayloadInPayloadGroup" Permanent="yes" DetectCondition="none"> | ||
| 6 | <PayloadGroupRef Id="PackagePayloadGroup" /> | ||
| 7 | </ExePackage> | ||
| 8 | </PackageGroup> | ||
| 9 | </Fragment> | ||
| 10 | <Fragment> | ||
| 11 | <PayloadGroup Id="PackagePayloadGroup"> | ||
| 12 | <ExePackagePayload SourceFile="burn.exe" /> | ||
| 13 | </PayloadGroup> | ||
| 14 | </Fragment> | ||
| 15 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs new file mode 100644 index 00000000..3c361c49 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MspPackage Id="SpecifiedHash"> | ||
| 6 | <MspPackagePayload SourceFile="example.msp" DownloadUrl="example.com" Hash="abcd" /> | ||
| 7 | </MspPackage> | ||
| 8 | </PackageGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs new file mode 100644 index 00000000..8e62f660 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsuPackage Id="SpecifiedHashAndMissingDownloadUrl" Permanent="yes" DetectCondition="none"> | ||
| 6 | <MsuPackagePayload Name="example.msu" Hash="abcd" Size="1" Version="1.0.0.0" ProductName="KB1234567" Description="fake msu" /> | ||
| 7 | </MsuPackage> | ||
| 8 | </PackageGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs new file mode 100644 index 00000000..f79da874 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none"> | ||
| 6 | <ExePackagePayload SourceFile="example.exe" Hash="abcd" /> | ||
| 7 | </ExePackage> | ||
| 8 | </PackageGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs new file mode 100644 index 00000000..dda306cf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <MsiPackage Id="WrongPackagePayloadInPayloadGroup"> | ||
| 6 | <PayloadGroupRef Id="WrongPackagePayloadGroup" /> | ||
| 7 | </MsiPackage> | ||
| 8 | </PackageGroup> | ||
| 9 | </Fragment> | ||
| 10 | <Fragment> | ||
| 11 | <PayloadGroup Id="WrongPackagePayloadGroup"> | ||
| 12 | <ExePackagePayload SourceFile="burn.exe" /> | ||
| 13 | </PayloadGroup> | ||
| 14 | </Fragment> | ||
| 15 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt | |||
| @@ -0,0 +1 @@ | |||
| This is A v1.0.0 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt new file mode 100644 index 00000000..b1f0bc01 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt | |||
| @@ -0,0 +1 @@ | |||
| This ia A v1.0.1 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt new file mode 100644 index 00000000..ece55fec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt | |||
| @@ -0,0 +1 @@ | |||
| This is B v1.0.0 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt new file mode 100644 index 00000000..cf3372fd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt | |||
| @@ -0,0 +1 @@ | |||
| This ia B v1.0.1 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs new file mode 100644 index 00000000..c9dcdd72 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="~Test Package" Version="$(var.V)" Manufacturer="Example Corporation" Language="1033" UpgradeCode="e703bf17-4765-444c-91fd-88550fa681d4" Scope="perMachine" ProductCode="e703bf17-4765-444c-91fd-88550fa681d4"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="Newer version already installed." /> | ||
| 6 | |||
| 7 | <Directory Id="TARGETDIR" Name="SourceDir"> | ||
| 8 | <Directory Id="ProgramFilesFolder"> | ||
| 9 | <Directory Id="INSTALLFOLDER" Name="~Test App" /> | ||
| 10 | </Directory> | ||
| 11 | </Directory> | ||
| 12 | |||
| 13 | <Feature Id="Main"> | ||
| 14 | <ComponentGroupRef Id="Components" /> | ||
| 15 | </Feature> | ||
| 16 | </Package> | ||
| 17 | |||
| 18 | <Fragment> | ||
| 19 | <ComponentGroup Id="Components" Directory="INSTALLFOLDER"> | ||
| 20 | <Component Id="A"> | ||
| 21 | <File Id="a.txt" Name="a.txt" Source="Av$(var.A).txt" /> | ||
| 22 | </Component> | ||
| 23 | <Component Id="B"> | ||
| 24 | <File Id="b.txt" Name="b.txt" Source="Bv$(var.B).txt" /> | ||
| 25 | </Component> | ||
| 26 | </ComponentGroup> | ||
| 27 | </Fragment> | ||
| 28 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs new file mode 100644 index 00000000..d39170c0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Patch AllowRemoval="yes" Manufacturer="FireGiant" MoreInfoURL="http://www.example.com/" DisplayName="~Test Patch v$(var.V)" Description="~Test Small Update Patch v($var.V)" Classification="Update"> | ||
| 3 | |||
| 4 | <Media Id="1" Cabinet="foo.cab"> | ||
| 5 | <PatchBaseline Id="RTM" /> | ||
| 6 | </Media> | ||
| 7 | |||
| 8 | <PatchFamilyRef Id="SamplePatchFamily" /> | ||
| 9 | </Patch> | ||
| 10 | |||
| 11 | <Fragment> | ||
| 12 | <PatchFamily Id="SamplePatchFamily" Version="$(var.V)" Supersede="yes"> | ||
| 13 | <ComponentRef Id="A" /> | ||
| 14 | </PatchFamily> | ||
| 15 | </Fragment> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs new file mode 100644 index 00000000..5cb8ede8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="~Test Package" Version="$(var.V)" Manufacturer="Example Corporation" Language="1033" UpgradeCode="{6B8097B9-A5D0-4BDE-B21E-AF6622DDCA01}" Scope="perMachine" ProductCode="{7C871EC1-1F89-4850-A6A9-D7A4C21769F6}"> | ||
| 3 | <MajorUpgrade DowngradeErrorMessage="Newer version already installed." /> | ||
| 4 | <MediaTemplate EmbedCab="yes" /> | ||
| 5 | |||
| 6 | <CustomAction Id="CAFromExtension" DllEntry="DoesntExist" BinaryRef="BinFromWir" /> | ||
| 7 | |||
| 8 | <Directory Id="TARGETDIR" Name="SourceDir"> | ||
| 9 | <Directory Id="ProgramFilesFolder"> | ||
| 10 | <Directory Id="INSTALLFOLDER" Name="~Test App" /> | ||
| 11 | </Directory> | ||
| 12 | </Directory> | ||
| 13 | |||
| 14 | <Feature Id="Main"> | ||
| 15 | <ComponentGroupRef Id="Components" /> | ||
| 16 | </Feature> | ||
| 17 | </Package> | ||
| 18 | |||
| 19 | <Fragment> | ||
| 20 | <ComponentGroup Id="Components" Directory="INSTALLFOLDER"> | ||
| 21 | <Component> | ||
| 22 | <File Source="$(sys.SOURCEFILEPATH)" /> | ||
| 23 | </Component> | ||
| 24 | |||
| 25 | <Component> | ||
| 26 | <RegistryValue Root="HKLM" Key="SOFTWARE\!(bind.property.ProductName)\Patch" Name="Version" Value="$(var.V)" /> | ||
| 27 | </Component> | ||
| 28 | </ComponentGroup> | ||
| 29 | </Fragment> | ||
| 30 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs new file mode 100644 index 00000000..52e87f64 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'> | ||
| 2 | <Patch | ||
| 3 | AllowRemoval="yes" | ||
| 4 | DisplayName="~Test Patch v$(var.V)" | ||
| 5 | Description="~Test Small Update Patch v$(var.V)" | ||
| 6 | MoreInfoURL="http://www.example.com/" | ||
| 7 | Manufacturer="Example Corporation" | ||
| 8 | Classification="Update"> | ||
| 9 | |||
| 10 | <Media Id="1" Cabinet="foo.cab"> | ||
| 11 | <PatchBaseline Id="RTM" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" /> | ||
| 12 | </Media> | ||
| 13 | |||
| 14 | <PatchFamily Id='SequenceFamily' Version='$(var.V)' /> | ||
| 15 | </Patch> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt | |||
| @@ -0,0 +1 @@ | |||
| This is A v1.0.0 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs new file mode 100644 index 00000000..dab959d5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="~Test Package" Version="$(V)" Manufacturer="Example Corporation" Language="1033" UpgradeCode="7d326855-e790-4a94-8611-5351f8321fca" Compressed="yes" Scope="perMachine" ProductCode="7d326855-e790-4a94-8611-5351f8321fca"> | ||
| 3 | |||
| 4 | <MajorUpgrade DowngradeErrorMessage="Newer version already installed." /> | ||
| 5 | <MediaTemplate EmbedCab="yes" /> | ||
| 6 | |||
| 7 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 8 | <Directory Id="INSTALLFOLDER" Name="~Test App" /> | ||
| 9 | </StandardDirectory> | ||
| 10 | |||
| 11 | <Feature Id="Main"> | ||
| 12 | <ComponentGroupRef Id="Components" /> | ||
| 13 | </Feature> | ||
| 14 | </Package> | ||
| 15 | |||
| 16 | <Fragment> | ||
| 17 | <ComponentGroup Id="Components" Directory="INSTALLFOLDER"> | ||
| 18 | <Component> | ||
| 19 | <File Id="a.txt" Name="a.txt" Source="A.txt" /> | ||
| 20 | </Component> | ||
| 21 | |||
| 22 | <Component> | ||
| 23 | <RegistryValue Root="HKLM" Key="SOFTWARE\!(bind.property.ProductName)\Patch" Name="Version" Value="$(V)" /> | ||
| 24 | </Component> | ||
| 25 | </ComponentGroup> | ||
| 26 | </Fragment> | ||
| 27 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs new file mode 100644 index 00000000..889b1220 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'> | ||
| 2 | <Patch | ||
| 3 | AllowRemoval="yes" | ||
| 4 | DisplayName="~Test Patch v$(V)" | ||
| 5 | Description="~Test Small Update Patch v$(V)" | ||
| 6 | MoreInfoURL="http://www.example.com/" | ||
| 7 | Manufacturer="Example Corporation" | ||
| 8 | Classification="Update"> | ||
| 9 | |||
| 10 | <Media Id="1" Cabinet="foo.cab"> | ||
| 11 | <PatchBaseline Id="RTM" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" /> | ||
| 12 | </Media> | ||
| 13 | |||
| 14 | <PatchFamily Id='SequenceFamily' Version='$(V)' /> | ||
| 15 | </Patch> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs new file mode 100644 index 00000000..4a8f5630 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <PackageGroup Id="BundlePackages"> | ||
| 4 | <MspPackage Id="PatchA" SourceFile="PatchA.msp" PerMachine="yes" /> | ||
| 5 | </PackageGroup> | ||
| 6 | </Fragment> | ||
| 7 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs new file mode 100644 index 00000000..7fb3cb56 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <PackageGroup Id="BundlePackages"> | ||
| 4 | <MspPackage Id="PatchA" SourceFile="PatchA.msp" PerMachine="yes" /> | ||
| 5 | <MspPackage Id="PatchB" SourceFile="PatchB.msp" PerMachine="yes" /> | ||
| 6 | </PackageGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs new file mode 100644 index 00000000..201d177b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <PackageGroup Id="BundlePackages"> | ||
| 4 | <MspPackage Id="PatchA" SourceFile="PatchA.msp" PerMachine="yes" /> | ||
| 5 | <MspPackage Id="PatchB" SourceFile="PatchB.msp" PerMachine="yes" /> | ||
| 6 | <MspPackage Id="PatchC" SourceFile="PatchC.msp" PerMachine="yes" /> | ||
| 7 | </PackageGroup> | ||
| 8 | </Fragment> | ||
| 9 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs new file mode 100644 index 00000000..62a89af3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package ProductCode="26309973-0A5E-4979-B142-98A6E064EDC0" Name="PackageA" Language="1033" Version="$(var.V)" Manufacturer="Example Corporation" | ||
| 3 | UpgradeCode="32B0396A-CE36-4570-B16E-F88FA42DC409" Scope="perMachine" Compressed="yes"> | ||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 6 | <MediaTemplate EmbedCab="yes" /> | ||
| 7 | |||
| 8 | <PropertyRef Id="TestVersion"/> | ||
| 9 | |||
| 10 | <Feature Id="Complete" Level="1"> | ||
| 11 | <ComponentRef Id="FileComponent"/> | ||
| 12 | <ComponentRef Id="RegistryComponent"/> | ||
| 13 | <ComponentRef Id="RegistryComponent2" /> | ||
| 14 | </Feature> | ||
| 15 | </Package> | ||
| 16 | |||
| 17 | <Fragment> | ||
| 18 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 19 | <Directory Id="INSTALLFOLDER" Name="~Test A" /> | ||
| 20 | </StandardDirectory> | ||
| 21 | </Fragment> | ||
| 22 | |||
| 23 | <Fragment> | ||
| 24 | <Component Id="FileComponent" Directory="INSTALLFOLDER"> | ||
| 25 | <File Source="$(sys.SOURCEFILEPATH)"/> | ||
| 26 | </Component> | ||
| 27 | </Fragment> | ||
| 28 | |||
| 29 | <Fragment> | ||
| 30 | <Component Id="RegistryComponent" Directory="INSTALLFOLDER"> | ||
| 31 | <RegistryValue Root="HKLM" Key="Software\WiX\Tests\$(var.A)" Name="A" Value="!(bind.Property.TestVersion)" Type="string" /> | ||
| 32 | </Component> | ||
| 33 | </Fragment> | ||
| 34 | |||
| 35 | <Fragment> | ||
| 36 | <Component Id="RegistryComponent2" Directory="INSTALLFOLDER"> | ||
| 37 | <RegistryValue Root="HKLM" Key="Software\WiX\Tests\$(var.B)" Name="A2" Value="!(bind.Property.TestVersion)" Type="string" /> | ||
| 38 | </Component> | ||
| 39 | </Fragment> | ||
| 40 | |||
| 41 | <Fragment> | ||
| 42 | <Property Id="TestVersion" Value="$(var.V)"/> | ||
| 43 | </Fragment> | ||
| 44 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs new file mode 100644 index 00000000..1b01774c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Patch AllowRemoval="yes" Classification="Update" ClientPatchId="PatchA" Description="Patch A" DisplayName="Patch A" Manufacturer="Example Corporation" MinorUpdateTargetRTM="yes"> | ||
| 3 | <Media Id="100" Cabinet="A" EmbedCab="yes"> | ||
| 4 | <PatchBaseline Id="PatchA" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" /> | ||
| 5 | </Media> | ||
| 6 | |||
| 7 | <PatchFamily Id="A" Version="$(var.V)" Supersede="yes"> | ||
| 8 | <ComponentRef Id="RegistryComponent" /> | ||
| 9 | <PropertyRef Id="TestVersion" /> | ||
| 10 | </PatchFamily> | ||
| 11 | </Patch> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs new file mode 100644 index 00000000..f0630ead --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Patch AllowRemoval="yes" Classification="Update" ClientPatchId="PatchB" Description="Patch B" DisplayName="Patch B" Manufacturer="Example Corporation" MinorUpdateTargetRTM="yes"> | ||
| 3 | <Media Id="100" Cabinet="B" EmbedCab="yes"> | ||
| 4 | <PatchBaseline Id="PatchB" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb"> | ||
| 5 | <Validate ProductId="no" /> | ||
| 6 | </PatchBaseline> | ||
| 7 | </Media> | ||
| 8 | |||
| 9 | <PatchFamily Id="B" Version="$(var.V)" Supersede="yes"> | ||
| 10 | <ComponentRef Id="RegistryComponent" /> | ||
| 11 | <PropertyRef Id="TestVersion" /> | ||
| 12 | </PatchFamily> | ||
| 13 | </Patch> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs new file mode 100644 index 00000000..f9d2a55a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Patch AllowRemoval="yes" Classification="Update" ClientPatchId="PatchC" Description="Patch C" DisplayName="Patch C" Manufacturer="Example Corporation" MinorUpdateTargetRTM="yes"> | ||
| 3 | <Media Id="100" Cabinet="C" EmbedCab="yes"> | ||
| 4 | <PatchBaseline Id="PatchC" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb"> | ||
| 5 | <Validate ProductId="no" UpgradeCode="no" /> | ||
| 6 | </PatchBaseline> | ||
| 7 | </Media> | ||
| 8 | |||
| 9 | <PatchFamily Id="C" Version="$(var.V)" Supersede="yes"> | ||
| 10 | <ComponentRef Id="RegistryComponent" /> | ||
| 11 | <PropertyRef Id="TestVersion" /> | ||
| 12 | </PatchFamily> | ||
| 13 | </Patch> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt | |||
| @@ -0,0 +1 @@ | |||
| This is A v1.0.0 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt new file mode 100644 index 00000000..b1f0bc01 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt | |||
| @@ -0,0 +1 @@ | |||
| This ia A v1.0.1 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt new file mode 100644 index 00000000..ece55fec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt | |||
| @@ -0,0 +1 @@ | |||
| This is B v1.0.0 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt new file mode 100644 index 00000000..cf3372fd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt | |||
| @@ -0,0 +1 @@ | |||
| This ia B v1.0.1 | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs new file mode 100644 index 00000000..bc460636 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <PackageGroup Id="BundlePackages"> | ||
| 4 | <MsiPackage Id="PackageA" SourceFile="Baseline.msi"> | ||
| 5 | <SlipstreamMsp Id="PatchA" /> | ||
| 6 | </MsiPackage> | ||
| 7 | <MspPackage Id="PatchA" SourceFile="Patch1.msp" /> | ||
| 8 | </PackageGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs new file mode 100644 index 00000000..e3845382 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="~Test Package" Version="$(var.V)" Manufacturer="Example Corporation" Language="1033" UpgradeCode="7d326855-e790-4a94-8611-5351f8321fca" Compressed="yes" Scope="perMachine" ProductCode="7d326855-e790-4a94-8611-5351f8321fca"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="Newer version already installed." /> | ||
| 6 | <MediaTemplate EmbedCab="yes" /> | ||
| 7 | |||
| 8 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 9 | <Directory Id="INSTALLFOLDER" Name="~Test A" /> | ||
| 10 | </StandardDirectory> | ||
| 11 | |||
| 12 | <Feature Id="Main"> | ||
| 13 | <ComponentGroupRef Id="Components" /> | ||
| 14 | </Feature> | ||
| 15 | </Package> | ||
| 16 | |||
| 17 | <Fragment> | ||
| 18 | <ComponentGroup Id="Components" Directory="INSTALLFOLDER"> | ||
| 19 | <Component> | ||
| 20 | <File Id="a.txt" Name="a.txt" Source="Av$(var.A).txt" /> | ||
| 21 | </Component> | ||
| 22 | <Component> | ||
| 23 | <File Id="b.txt" Name="b.txt" Source="Bv$(var.B).txt" /> | ||
| 24 | </Component> | ||
| 25 | </ComponentGroup> | ||
| 26 | </Fragment> | ||
| 27 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs new file mode 100644 index 00000000..52e87f64 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'> | ||
| 2 | <Patch | ||
| 3 | AllowRemoval="yes" | ||
| 4 | DisplayName="~Test Patch v$(var.V)" | ||
| 5 | Description="~Test Small Update Patch v$(var.V)" | ||
| 6 | MoreInfoURL="http://www.example.com/" | ||
| 7 | Manufacturer="Example Corporation" | ||
| 8 | Classification="Update"> | ||
| 9 | |||
| 10 | <Media Id="1" Cabinet="foo.cab"> | ||
| 11 | <PatchBaseline Id="RTM" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" /> | ||
| 12 | </Media> | ||
| 13 | |||
| 14 | <PatchFamily Id='SequenceFamily' Version='$(var.V)' /> | ||
| 15 | </Patch> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs new file mode 100644 index 00000000..dc94d688 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <PayloadGroup Id="AbsoluteName"> | ||
| 4 | <Payload SourceFile="dir\file.ext" Name="\\server\share\target.file" /> | ||
| 5 | <Payload SourceFile="dir\file.ext" Name="C:\target.file" /> | ||
| 6 | <Payload SourceFile="dir\file.ext" Name="\dir\target.file" /> | ||
| 7 | </PayloadGroup> | ||
| 8 | </Fragment> | ||
| 9 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs new file mode 100644 index 00000000..544b80ec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <PayloadGroup Id="CanonicalizeName"> | ||
| 4 | <Payload SourceFile="dir\file.ext" Name="a\..\c\.\d.exe" /> | ||
| 5 | </PayloadGroup> | ||
| 6 | </Fragment> | ||
| 7 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs new file mode 100644 index 00000000..f8f38ea6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Bundle Name="DownloadUrlPlaceholders" Version="1.0.0.0" Manufacturer="test" UpgradeCode="{B04C20B8-70C3-4DE1-8D91-4F11C7C68DED}"> | ||
| 3 | <BootstrapperApplicationRef Id="fakeba" /> | ||
| 4 | |||
| 5 | <Chain> | ||
| 6 | <PackageGroupRef Id="ContainerPackages" /> | ||
| 7 | <PackageGroupRef Id="UncompressedPackages" /> | ||
| 8 | </Chain> | ||
| 9 | |||
| 10 | <PayloadGroupRef Id="LayoutOnlyPayloads" /> | ||
| 11 | <Container Id="PackagesContainer" Name="packages.cab" DownloadUrl="http://example.com/{0}id/{1}/{2}"> | ||
| 12 | <PackageGroupRef Id="ContainerPackages" /> | ||
| 13 | </Container> | ||
| 14 | </Bundle> | ||
| 15 | <Fragment> | ||
| 16 | <PackageGroup Id="ContainerPackages"> | ||
| 17 | <ExePackage SourceFile="burn.exe" DetectCondition="none" Compressed="no" /> | ||
| 18 | </PackageGroup> | ||
| 19 | </Fragment> | ||
| 20 | <Fragment> | ||
| 21 | <PackageGroup Id="UncompressedPackages"> | ||
| 22 | <MsiPackage SourceFile="test.msi" DownloadUrl="http://example.com/{0}id/{1}/{2}" Compressed="no" /> | ||
| 23 | </PackageGroup> | ||
| 24 | </Fragment> | ||
| 25 | <Fragment> | ||
| 26 | <PayloadGroup Id="LayoutOnlyPayloads"> | ||
| 27 | <Payload Id="LayoutOnlyPayload" SourceFile="$(sys.SOURCEFILEPATH)" DownloadUrl="http://example.com/{0}id/{1}/{2}" Compressed="no" /> | ||
| 28 | </PayloadGroup> | ||
| 29 | </Fragment> | ||
| 30 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs new file mode 100644 index 00000000..5263cbd4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage SourceFile="burn.exe" DetectCondition="none"> | ||
| 6 | <PayloadGroupRef Id="Shared" /> | ||
| 7 | </ExePackage> | ||
| 8 | </PackageGroup> | ||
| 9 | <BootstrapperApplication> | ||
| 10 | <PayloadGroupRef Id="Shared" /> | ||
| 11 | </BootstrapperApplication> | ||
| 12 | <PayloadGroup Id="Shared"> | ||
| 13 | <Payload SourceFile="$(sys.SOURCEFILEPATH)" /> | ||
| 14 | </PayloadGroup> | ||
| 15 | </Fragment> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs new file mode 100644 index 00000000..9c37a27d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <PayloadGroup Id="ValidName"> | ||
| 4 | <Payload SourceFile="dir\file.ext" Name="dir\file.ext" /> | ||
| 5 | </PayloadGroup> | ||
| 6 | </Fragment> | ||
| 7 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs new file mode 100644 index 00000000..68d115c5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <?define Test = "$(env.CommonProgramFiles(x86))" ?> | ||
| 4 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs new file mode 100644 index 00000000..37a2c462 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="example.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs new file mode 100644 index 00000000..5bf78a9d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package ProductCode="8738B0C5-C4AA-4634-8C03-11EAA2F1E15D" Name="~TagTestPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a"> | ||
| 3 | |||
| 4 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 5 | |||
| 6 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 7 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 8 | </Feature> | ||
| 9 | |||
| 10 | <SoftwareTag Regid="wixtoolset.org" InstallDirectory="INSTALLFOLDER" /> | ||
| 11 | </Package> | ||
| 12 | |||
| 13 | <Fragment> | ||
| 14 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 15 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 16 | </StandardDirectory> | ||
| 17 | </Fragment> | ||
| 18 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt | |||
| @@ -0,0 +1 @@ | |||
| This is example.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs new file mode 100644 index 00000000..f62bbd0e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="MinimalComponentGroup" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs new file mode 100644 index 00000000..433be7f0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <?ifndef ProductCode?> | ||
| 3 | <?define ProductCode = *?> | ||
| 4 | <?endif?> | ||
| 5 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="12E4699F-E774-4D05-8A01-5BDD41BBA127" Compressed="no" Scope="perMachine" ProductCode="$(var.ProductCode)"> | ||
| 6 | |||
| 7 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 8 | |||
| 9 | <Feature Id="ProductFeature" Title="MsiPackageTitle"> | ||
| 10 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 11 | </Feature> | ||
| 12 | </Package> | ||
| 13 | |||
| 14 | <Fragment> | ||
| 15 | <StandardDirectory Id="ProgramFiles6432Folder"> | ||
| 16 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 17 | </StandardDirectory> | ||
| 18 | </Fragment> | ||
| 19 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs new file mode 100644 index 00000000..0621eb8d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="ProgIdComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8"> | ||
| 6 | <File Source="test.txt" Name="ProgIdComp.txt"></File> | ||
| 7 | <Class Id="F12A6F69-117F-471F-AE73-F8E74218F498" Advertise="yes" Context="LocalServer32" Description="FakeClassF12A" ThreadingModel="apartment" Version="0.0.0.1"> | ||
| 8 | <ProgId Id="73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D" NoOpen="NoOpen73E7" /> | ||
| 9 | </Class> | ||
| 10 | </Component> | ||
| 11 | </ComponentGroup> | ||
| 12 | </Fragment> | ||
| 13 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs new file mode 100644 index 00000000..d3b31db5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="ProgId" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 9 | </Feature> | ||
| 10 | </Package> | ||
| 11 | |||
| 12 | <Fragment> | ||
| 13 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 14 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 15 | </StandardDirectory> | ||
| 16 | </Fragment> | ||
| 17 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs new file mode 100644 index 00000000..5166be16 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Name="Foo.exe" Source="test.txt" /> | ||
| 7 | <ProgId Id="Foo.File.hol.15" Advertise="yes" Description="Foo Holiday File"> | ||
| 8 | <ProgId Id="Foo.File.hol" /> | ||
| 9 | <Extension Id="hol"> | ||
| 10 | <Verb Id="Open" Argument="/hol "%1"" Sequence="1" /> | ||
| 11 | </Extension> | ||
| 12 | </ProgId> | ||
| 13 | </Component> | ||
| 14 | </ComponentGroup> | ||
| 15 | </Fragment> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs new file mode 100644 index 00000000..8f4f661d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 4 | |||
| 5 | <Feature Id="ProductFeature1" Title="!(loc.FeatureTitle)"> | ||
| 6 | <ComponentRef Id="Component1" Primary="yes" /> | ||
| 7 | </Feature> | ||
| 8 | |||
| 9 | <Feature Id="ProductFeature2" Title="!(loc.FeatureTitle)"> | ||
| 10 | <ComponentRef Id="Component1" /> | ||
| 11 | <ComponentRef Id="Component2" /> | ||
| 12 | </Feature> | ||
| 13 | </Package> | ||
| 14 | |||
| 15 | <Fragment> | ||
| 16 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 17 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 18 | </StandardDirectory> | ||
| 19 | </Fragment> | ||
| 20 | |||
| 21 | <Fragment> | ||
| 22 | <Component Id="Component1" Directory="INSTALLFOLDER" Guid="C8EFA5DF-2876-4724-A003-A6BEBF140BB1"> | ||
| 23 | <File Id="File1" Source="test.txt" /> | ||
| 24 | <Category Id="{BD245B5A-EC33-46ED-98FF-E9D3D416AD04}" AppData="AppData1" Qualifier="Qualifier1" /> | ||
| 25 | </Component> | ||
| 26 | </Fragment> | ||
| 27 | |||
| 28 | <Fragment> | ||
| 29 | <Component Id="Component2" Directory="INSTALLFOLDER" Guid="8DE79DE7-4B55-4D43-88F5-AD6A1E8D242A"> | ||
| 30 | <File Id="File2" Source="test.txt" /> | ||
| 31 | <Category Id="{0A82C8F6-9CE9-4336-B8BE-91A39B5F7081}" AppData="AppData2" Qualifier="Qualifier2" /> | ||
| 32 | </Component> | ||
| 33 | </Fragment> | ||
| 34 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs new file mode 100644 index 00000000..452aea69 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <RegistryKey Root="HKLM" Key="Software\Acme\Foobar 1.0"> | ||
| 7 | <RegistryValue Type="string" Name="InstallDir" Value="[TARGETDIR]" /> | ||
| 8 | <RegistryValue Type="string" Name="InstallDir" Value="[INSTALLDIR]" /> | ||
| 9 | <RegistryValue Type="string" Name="InstallDir" Value="[ProgramFilesFolder]" /> | ||
| 10 | </RegistryKey> | ||
| 11 | </Component> | ||
| 12 | </ComponentGroup> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs new file mode 100644 index 00000000..1fb2e906 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component Id="MiscComponent" Guid="7C40C257-AB36-4B8C-8FD1-C56E0AC4AAEF"> | ||
| 6 | <RegistryKey Id="reg1" Root="HKLM" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes" Key="Software\WBM\WB\"> | ||
| 7 | <RegistryValue Id="reg2" Type="string" Name="InstallationPath" Value="[INSTALLFOLDER]" /> | ||
| 8 | </RegistryKey> | ||
| 9 | </Component> | ||
| 10 | </ComponentGroup> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs new file mode 100644 index 00000000..fe6e179e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component Id="MiscComponent" Guid="7C40C257-AB36-4B8C-8FD1-C56E0AC4AAEF"> | ||
| 6 | <RegistryValue Root="HKLM" Key="Path\To\Key" Value="1.0.1234.123" Type="string" KeyPath="yes" /> | ||
| 7 | <RegistryValue Root="HKLM" Key="Path\To\AnotherKey" Name="Secret" Type="binary" /> | ||
| 8 | </Component> | ||
| 9 | </ComponentGroup> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs new file mode 100644 index 00000000..c62c571d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component Id="MultiStringComponent" Guid="7C40C257-AB36-4B8C-8FD1-C56E0AC4AAEF"> | ||
| 6 | <RegistryValue Root="HKLM" Key="Path\To\Key" Type="multiString" KeyPath="yes"> | ||
| 7 | <MultiString Value="a" /> | ||
| 8 | <MultiStringValue Value="b" /> | ||
| 9 | <MultiStringValue /> | ||
| 10 | <MultiString Value="c" /> | ||
| 11 | <MultiStringValue /> | ||
| 12 | </RegistryValue> | ||
| 13 | <RegistryValue Root="HKLM" Key="Path\To\AnotherKey" Name="Secret" Type="binary" /> | ||
| 14 | </Component> | ||
| 15 | </ComponentGroup> | ||
| 16 | </Fragment> | ||
| 17 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs new file mode 100644 index 00000000..a55a1e18 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="RemoveRegistryKeyComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8"> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | <RemoveRegistryKey Id="RemoveAKeyName" Action="removeOnUninstall" Root="HKLM" Key="AKeyName" /> | ||
| 8 | </Component> | ||
| 9 | </ComponentGroup> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs new file mode 100644 index 00000000..3218295b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="ReserveCostComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8"> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | <ReserveCost Id="TestCost" RunFromSource="200" RunLocal="100"></ReserveCost> | ||
| 8 | </Component> | ||
| 9 | </ComponentGroup> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs new file mode 100644 index 00000000..ecfccfcb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <RollbackBoundary Id="nonvital" Vital="no" /> | ||
| 6 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 7 | </PackageGroup> | ||
| 8 | </Fragment> | ||
| 9 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs new file mode 100644 index 00000000..bbad63e6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component Subdirectory="a"> | ||
| 6 | <File Source="a\test.txt" /> | ||
| 7 | </Component> | ||
| 8 | <Component Subdirectory="b"> | ||
| 9 | <File Source="b\test.txt" /> | ||
| 10 | </Component> | ||
| 11 | <Component Subdirectory="c"> | ||
| 12 | <File Source="c\test.txt" /> | ||
| 13 | </Component> | ||
| 14 | </ComponentGroup> | ||
| 15 | </Fragment> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt new file mode 100644 index 00000000..1970cae6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is a\test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt new file mode 100644 index 00000000..fa2c7082 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is b\test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt new file mode 100644 index 00000000..1c0cbda6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is c\test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs new file mode 100644 index 00000000..d5379e7b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{74C29381-1915-4948-B8B4-5646806A0BD4}"> | ||
| 3 | <CustomAction Id="CustomAction2" Property="TestAdvtExecuteSequenceProperty" Value="1" /> | ||
| 4 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 5 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="ykd0udtb"> | ||
| 6 | <Component Id="test.txt" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always32"> | ||
| 7 | <File Id="test.txt" Name="test.txt" KeyPath="yes" Source="MsiPackage\test.txt" /> | ||
| 8 | </Component> | ||
| 9 | </Directory> | ||
| 10 | </StandardDirectory> | ||
| 11 | <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle"> | ||
| 12 | <ComponentRef Id="test.txt" /> | ||
| 13 | </Feature> | ||
| 14 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 15 | <Media Id="1" /> | ||
| 16 | <InstallExecuteSequence> | ||
| 17 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 18 | </InstallExecuteSequence> | ||
| 19 | <InstallUISequence> | ||
| 20 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 21 | </InstallUISequence> | ||
| 22 | <AdminExecuteSequence> | ||
| 23 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 24 | </AdminExecuteSequence> | ||
| 25 | <AdminUISequence> | ||
| 26 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 27 | </AdminUISequence> | ||
| 28 | <AdvertiseExecuteSequence> | ||
| 29 | <Custom Action="CustomAction2" After="CostInitialize" /> | ||
| 30 | </AdvertiseExecuteSequence> | ||
| 31 | </Package> | ||
| 32 | </Wix> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi new file mode 100644 index 00000000..7f894091 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs new file mode 100644 index 00000000..65cba20e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Directory="INSTALLFOLDER"> | ||
| 6 | <File Id="test.txt" Source="test.txt" /> | ||
| 7 | <ServiceInstall Name="SampleService" ErrorControl="ignore" Start="disabled" Type="ownProcess" /> | ||
| 8 | <ServiceControl Name="SampleService" Start="install" Stop="uninstall" Remove="uninstall" Wait="yes" /> | ||
| 9 | </Component> | ||
| 10 | </ComponentGroup> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs new file mode 100644 index 00000000..d3f8accf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 9 | </Feature> | ||
| 10 | |||
| 11 | <SetProperty Id="INSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" /> | ||
| 12 | </Package> | ||
| 13 | |||
| 14 | <Fragment> | ||
| 15 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 16 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 17 | </StandardDirectory> | ||
| 18 | </Fragment> | ||
| 19 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs new file mode 100644 index 00000000..7e8f2e99 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 6 | </PackageGroup> | ||
| 7 | |||
| 8 | <SetVariable Id="SetCoercedNumber" Variable="CoercedNumber" Value="2" /> | ||
| 9 | <SetVariable Id="SetCoercedString" Variable="CoercedString" Value="Bar" /> | ||
| 10 | <SetVariable Id="SetCoercedVersion" Variable="CoercedVersion" Value="v2.0" /> | ||
| 11 | <SetVariable Id="SetNeedsFormatting" Variable="NeedsFormatting" Value="[One] [Two] [Three]" /> | ||
| 12 | <SetVariable Id="SetUnset" Variable="Unset" Condition="VersionString = v2.0" After="SetVersionString" /> | ||
| 13 | <SetVariable Id="SetVersionString" Variable="VersionString" Value="v1.0" Type="string" /> | ||
| 14 | </Fragment> | ||
| 15 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs new file mode 100644 index 00000000..f16fce0d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage SourceFile="C:\Windows\system32\credwiz.exe" Permanent="yes" DetectCondition="none"> | ||
| 6 | <PayloadGroupRef Id="SharedPayloads" /> | ||
| 7 | </ExePackage> | ||
| 8 | <ExePackage SourceFile="C:\Windows\system32\cscript.exe" Permanent="yes" DetectCondition="none"> | ||
| 9 | <PayloadGroupRef Id="SharedPayloads" /> | ||
| 10 | </ExePackage> | ||
| 11 | </PackageGroup> | ||
| 12 | </Fragment> | ||
| 13 | <Fragment> | ||
| 14 | <PayloadGroup Id="SharedPayloads"> | ||
| 15 | <Payload Id="SourceFilePayload" SourceFile="$(sys.SOURCEFILEPATH)" /> | ||
| 16 | </PayloadGroup> | ||
| 17 | </Fragment> | ||
| 18 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs new file mode 100644 index 00000000..da1e4f38 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{6CA94D1D-B568-4ED6-9EBC-3534C85970BB}"> | ||
| 3 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 4 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="ykd0udtb"> | ||
| 5 | <Component Id="ShortcutComp" Guid="{5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8}" Bitness="always32"> | ||
| 6 | <File Id="test.txt" Name="test.txt" KeyPath="yes" Source="MsiPackage\test.txt" /> | ||
| 7 | <Shortcut Id="FileTargetShortcut" Directory="INSTALLFOLDER" Name="FileTargetShortcut" ShortName="lm2tdtqp" Target="[#test.txt]" /> | ||
| 8 | <Shortcut Id="CustomTargetShortcut" Directory="INSTALLFOLDER" Name="Planner" ShortName="PLANNER" Target="[INSTALLFOLDER]custom.target" /> | ||
| 9 | <Shortcut Id="AdvtShortcut" Directory="INSTALLFOLDER" Name="AdvtShortcut" ShortName="mdbqel9r" Advertise="yes" /> | ||
| 10 | </Component> | ||
| 11 | </Directory> | ||
| 12 | </StandardDirectory> | ||
| 13 | <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle"> | ||
| 14 | <ComponentRef Id="ShortcutComp" Primary="yes" /> | ||
| 15 | </Feature> | ||
| 16 | <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> | ||
| 17 | <Media Id="1" /> | ||
| 18 | </Package> | ||
| 19 | </Wix> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs new file mode 100644 index 00000000..27f2ab9b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="ShortcutComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8"> | ||
| 6 | <File Source="test.txt"> | ||
| 7 | <Shortcut Id="TheShortcut" Name="d" Directory="INSTALLFOLDER"> | ||
| 8 | <ShortcutProperty Key="CustomShortcutKey" Value="CustomShortcutValue"></ShortcutProperty> | ||
| 9 | </Shortcut> | ||
| 10 | </File> | ||
| 11 | </Component> | ||
| 12 | </ComponentGroup> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs new file mode 100644 index 00000000..d704bbf1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="ShortcutComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8"> | ||
| 6 | <File Source="test.txt"> | ||
| 7 | <Shortcut Name="DaName" ShortName="DANAME" Directory="INSTALLFOLDER" /> | ||
| 8 | </File> | ||
| 9 | </Component> | ||
| 10 | </ComponentGroup> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi new file mode 100644 index 00000000..8737f3c2 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl new file mode 100644 index 00000000..bc1dee83 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="BundleName">~TestBundle</String> | ||
| 9 | |||
| 10 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs new file mode 100644 index 00000000..21749c07 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Bundle Name="!(loc.BundleName)" Version="!(bind.packageVersion.test.msi)" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a"> | ||
| 3 | <BootstrapperApplication> | ||
| 4 | <BootstrapperApplicationDll SourceFile="fakeba.dll" /> | ||
| 5 | </BootstrapperApplication> | ||
| 6 | <Chain> | ||
| 7 | <MsiPackage SourceFile="test.msi"> | ||
| 8 | <MsiProperty Name="TEST" Value="1" /> | ||
| 9 | </MsiPackage> | ||
| 10 | </Chain> | ||
| 11 | </Bundle> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs new file mode 100644 index 00000000..f5fe9885 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <BootstrapperApplication Id="fakeba"> | ||
| 4 | <BootstrapperApplicationDll SourceFile="fakeba.dll" /> | ||
| 5 | </BootstrapperApplication> | ||
| 6 | </Fragment> | ||
| 7 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs new file mode 100644 index 00000000..48f53ae3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Bundle Name="!(loc.BundleName)" Version="!(bind.packageVersion.test.msi)" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a"> | ||
| 4 | <BootstrapperApplicationRef Id="fakeba"> | ||
| 5 | <PayloadGroupRef Id="TestPayloadGroup" /> | ||
| 6 | </BootstrapperApplicationRef> | ||
| 7 | <Chain> | ||
| 8 | <MsiPackage SourceFile="test.msi"> | ||
| 9 | <MsiProperty Name="TEST" Value="1" /> | ||
| 10 | </MsiPackage> | ||
| 11 | </Chain> | ||
| 12 | </Bundle> | ||
| 13 | <Fragment> | ||
| 14 | <PayloadGroup Id="TestPayloadGroup"> | ||
| 15 | <Payload SourceFile="MsiPackage\test.txt" /> | ||
| 16 | </PayloadGroup> | ||
| 17 | </Fragment> | ||
| 18 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll new file mode 100644 index 00000000..0e461ba8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll | |||
| @@ -0,0 +1 @@ | |||
| This is Shared.dll. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt new file mode 100644 index 00000000..8b986220 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll new file mode 100644 index 00000000..970efdf0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll | |||
| @@ -0,0 +1 @@ | |||
| This is a fakeba.dll \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi new file mode 100644 index 00000000..0722d60e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm new file mode 100644 index 00000000..6f179aba --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm | |||
| Binary files differ | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs new file mode 100644 index 00000000..3c999812 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="yes" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <MergeRef Id="TestMsm" /> | ||
| 9 | </Feature> | ||
| 10 | </Package> | ||
| 11 | |||
| 12 | <Fragment> | ||
| 13 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 14 | <Directory Id="INSTALLFOLDER" Name="MsiPackage"> | ||
| 15 | <!-- --> | ||
| 16 | <Merge Id="TestMsm" Language="1033" SourceFile="test.msm" /> | ||
| 17 | </Directory> | ||
| 18 | </StandardDirectory> | ||
| 19 | </Fragment> | ||
| 20 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl new file mode 100644 index 00000000..c74e86a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="Manufacturer">Example Company</String> | ||
| 9 | |||
| 10 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj new file mode 100644 index 00000000..597d4318 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| 3 | <PropertyGroup> | ||
| 4 | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
| 5 | <Platform Condition=" '$(Platform)' == '' ">x86</Platform> | ||
| 6 | <ProductVersion>0.9</ProductVersion> | ||
| 7 | <ProjectGuid>27df04c6-3cef-4b9a-bac6-4e78d188384f</ProjectGuid> | ||
| 8 | <OutputName>MergeModule1</OutputName> | ||
| 9 | <OutputType>Module</OutputType> | ||
| 10 | <Name>MergeModule1</Name> | ||
| 11 | <RootNamespace>MergeModule1</RootNamespace> | ||
| 12 | </PropertyGroup> | ||
| 13 | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> | ||
| 14 | <PlatformName>$(Platform)</PlatformName> | ||
| 15 | <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath> | ||
| 16 | <DefineConstants>Debug</DefineConstants> | ||
| 17 | </PropertyGroup> | ||
| 18 | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> | ||
| 19 | <PlatformName>$(Platform)</PlatformName> | ||
| 20 | <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath> | ||
| 21 | </PropertyGroup> | ||
| 22 | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' "> | ||
| 23 | <PlatformName>$(Platform)</PlatformName> | ||
| 24 | <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath> | ||
| 25 | <DefineConstants>Debug</DefineConstants> | ||
| 26 | </PropertyGroup> | ||
| 27 | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' "> | ||
| 28 | <PlatformName>$(Platform)</PlatformName> | ||
| 29 | <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath> | ||
| 30 | </PropertyGroup> | ||
| 31 | <ItemGroup> | ||
| 32 | <Compile Include="MergeModule.wxs" /> | ||
| 33 | </ItemGroup> | ||
| 34 | <ItemGroup> | ||
| 35 | <EmbeddedResource Include="MergeModule.en-us.wxl" /> | ||
| 36 | </ItemGroup> | ||
| 37 | <ItemGroup> | ||
| 38 | <WixExtension Include="FgwepExtension.wixext"> | ||
| 39 | <Name>FgwepExtension.wixext</Name> | ||
| 40 | <HintPath>$(WixExtDir)\FgwepExtension.wixext.dll</HintPath> | ||
| 41 | </WixExtension> | ||
| 42 | </ItemGroup> | ||
| 43 | <Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " /> | ||
| 44 | <Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets') " /> | ||
| 45 | <Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' "> | ||
| 46 | <Error Text="FG-WiX or WiX Toolset build tools (v3.11 or later) must be installed to build this project. To download FG-WiX, go to https://www.firegiant.com/downloads/. To download the WiX Toolset, go to http://wixtoolset.org/releases/." /> | ||
| 47 | </Target> | ||
| 48 | </Project> \ No newline at end of file | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs new file mode 100644 index 00000000..8317e7af --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Module Id="MergeModule1" Language="1033" Version="1.0.0.0" Guid="243FB739-4D05-472F-9CFB-EF6B1017B6DE" InstallerVersion="200"> | ||
| 3 | <SummaryInformation Manufacturer="!(loc.Manufacturer)" /> | ||
| 4 | |||
| 5 | <Directory Id="MergeRedirectFolder"> | ||
| 6 | <Component Id="ModuleComponent1" Guid="A04E61B2-3ED4-4803-B2EB-4B773576FA45"> | ||
| 7 | <File Id="File1" Source="test.txt" /> | ||
| 8 | </Component> | ||
| 9 | </Directory> | ||
| 10 | |||
| 11 | <Directory Id="NotTheMergeRedirectFolder"> | ||
| 12 | <Component Id="ModuleComponent2" Guid="EADB3047-BD32-417B-AABF-B8D9CCDC22DA"> | ||
| 13 | <File Id="File2" Source="test.txt" /> | ||
| 14 | </Component> | ||
| 15 | </Directory> | ||
| 16 | </Module> | ||
| 17 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs new file mode 100644 index 00000000..cad1f049 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage DetectCondition="DetectedSomething" SourceFile="burn.exe" /> | ||
| 6 | </PackageGroup> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs new file mode 100644 index 00000000..0d459f02 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <PackageGroup Id="BundlePackages"> | ||
| 5 | <ExePackage | ||
| 6 | InstallArguments="/q /norestart "[WixBundleName]" /log "[NetFx462FullLog].html"" | ||
| 7 | UninstallArguments="/uninstall /q /norestart "[WixBundleName]" /log "[NetFx462FullLog].html"" | ||
| 8 | PerMachine="yes" | ||
| 9 | DetectCondition="A" | ||
| 10 | InstallCondition="B" | ||
| 11 | Id="NetFx462Web" | ||
| 12 | Vital="yes" | ||
| 13 | Permanent="yes" | ||
| 14 | Protocol="netfx4" | ||
| 15 | LogPathVariable="NetFx462FullLog"> | ||
| 16 | <ExePackagePayload | ||
| 17 | DownloadUrl="C" | ||
| 18 | Name="NDP462-KB3151802-Web.exe" | ||
| 19 | Description="Microsoft .NET Framework 4.6.2 Setup" | ||
| 20 | Hash="C42E6ED280290648BBD59F664008852F4CFE4548" | ||
| 21 | ProductName="Microsoft .NET Framework 4.6.2" | ||
| 22 | Size="9223372036854775807" | ||
| 23 | Version="4.6.1590.0" /> | ||
| 24 | </ExePackage> | ||
| 25 | </PackageGroup> | ||
| 26 | </Fragment> | ||
| 27 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs new file mode 100644 index 00000000..d7b5bdc0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 9 | </Feature> | ||
| 10 | </Package> | ||
| 11 | |||
| 12 | <Fragment> | ||
| 13 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 14 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 15 | </StandardDirectory> | ||
| 16 | </Fragment> | ||
| 17 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs new file mode 100644 index 00000000..b8e9f59c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | <Component Id="Shared.dll" Shared="yes"> | ||
| 9 | <File Name="Shared.dll" Source="test.txt" /> | ||
| 10 | </Component> | ||
| 11 | </ComponentGroup> | ||
| 12 | </Fragment> | ||
| 13 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs new file mode 100644 index 00000000..baa0c6b1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="65001" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="yes" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <?ifndef MediaTemplateCompressionLevel?> | ||
| 8 | <Media Id="1" Cabinet="example.cab" /> | ||
| 9 | <?elseif $(MediaTemplateCompressionLevel) = ""?> | ||
| 10 | <MediaTemplate /> | ||
| 11 | <?else?> | ||
| 12 | <MediaTemplate CabinetTemplate="low{0}.cab" CompressionLevel="$(MediaTemplateCompressionLevel)" /> | ||
| 13 | <?endif?> | ||
| 14 | |||
| 15 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 16 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 17 | </Feature> | ||
| 18 | </Package> | ||
| 19 | |||
| 20 | <Fragment> | ||
| 21 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 22 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 23 | </StandardDirectory> | ||
| 24 | </Fragment> | ||
| 25 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl new file mode 100644 index 00000000..c74e86a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="Manufacturer">Example Company</String> | ||
| 9 | |||
| 10 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs new file mode 100644 index 00000000..f4ce9c48 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Module Id="MergeModule1" Language="1033" Version="1.0.0.0" Guid="243FB739-4D05-472F-9CFB-EF6B1017B6DE"> | ||
| 3 | <SummaryInformation Manufacturer="!(loc.Manufacturer)" /> | ||
| 4 | |||
| 5 | <Property Id="Test" Hidden="true" SuppressModularization="true" /> | ||
| 6 | <CustomAction Id="Test" DllEntry="TestEntry" Execute="deferred" Return="check" Impersonate="no" HideTarget="yes" SuppressModularization="yes" BinaryRef="FakeCA" /> | ||
| 7 | |||
| 8 | <Binary Id="FakeCA" SourceFile="test.txt" /> | ||
| 9 | </Module> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs new file mode 100644 index 00000000..669de6ec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <UI Id="CustomUI"> | ||
| 9 | <TextStyle Id="FirstTextStyle" FaceName="Arial" Size="2" /> | ||
| 10 | </UI> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl new file mode 100644 index 00000000..77d46861 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | <String Id="CustomFontName"><!-- NameComment -->Tahoma</String> | ||
| 11 | <String Id="CustomFontSize"><!-- SizeComment -->8</String> | ||
| 12 | |||
| 13 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs new file mode 100644 index 00000000..a591fdd9 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <UI Id="CustomUI"> | ||
| 9 | <TextStyle Id="CustomFont" FaceName="!(loc.CustomFontName)" Size="!(loc.CustomFontSize)" /> | ||
| 10 | </UI> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs new file mode 100644 index 00000000..fa64f98f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <Component Id="TypeLibComp" Directory="INSTALLFOLDER" Guid="E85CB46B-BC22-4943-B678-5E399CBE53A6"> | ||
| 6 | <File Source="test.txt" Name="TypeLibComp.txt"></File> | ||
| 7 | <TypeLib Id="765BE8EE-BD7F-491E-90D2-C5A972462B50" Advertise="yes" Language="0" /> | ||
| 8 | </Component> | ||
| 9 | </ComponentGroup> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs new file mode 100644 index 00000000..587d8e95 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents"> | ||
| 5 | <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef> | ||
| 6 | </ComponentGroup> | ||
| 7 | |||
| 8 | <Upgrade Id="B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D"> | ||
| 9 | <UpgradeVersion Minimum="1.0.0" Property="RELPRODFOUND"></UpgradeVersion> | ||
| 10 | </Upgrade> | ||
| 11 | </Fragment> | ||
| 12 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs new file mode 100644 index 00000000..59839f30 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package ProductCode="{A81D50F9-B696-4F3D-ABE0-E64D61590E5F}" Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a"> | ||
| 3 | |||
| 4 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 5 | |||
| 6 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 7 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 8 | </Feature> | ||
| 9 | </Package> | ||
| 10 | |||
| 11 | <Fragment> | ||
| 12 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 13 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 14 | </StandardDirectory> | ||
| 15 | </Fragment> | ||
| 16 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs new file mode 100644 index 00000000..7e459e9a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Fragment> | ||
| 3 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 4 | <Component> | ||
| 5 | <File Source="example.txt" /> | ||
| 6 | <Provides Key="UsingProvides" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt | |||
| @@ -0,0 +1 @@ | |||
| This is example.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs new file mode 100644 index 00000000..7de55810 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | <?define Foo = "Foo" ?> | ||
| 2 | <?define Foo = "Foo" ?> | ||
| 3 | |||
| 4 | <?define Bar = "Bar" ?> | ||
| 5 | <?define Bar = "Baz" ?> | ||
| 6 | |||
| 7 | <?ifdef $(sys.WIXVERSION) ?> | ||
| 8 | <?if $(sys.WIXMAJORVERSION) >= 4 AND $(sys.WIXMAJORVERSION) < 5 ?> | ||
| 9 | <?warning WiX v4 is in effect! ?> | ||
| 10 | <?endif?> | ||
| 11 | <?endif?> | ||
| 12 | |||
| 13 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 14 | <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 15 | |||
| 16 | |||
| 17 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 18 | |||
| 19 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 20 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 21 | </Feature> | ||
| 22 | </Package> | ||
| 23 | |||
| 24 | <Fragment> | ||
| 25 | <Directory Id="TARGETDIR" Name="SourceDir"> | ||
| 26 | <Directory Id="ProgramFilesFolder"> | ||
| 27 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 28 | </Directory> | ||
| 29 | </Directory> | ||
| 30 | </Fragment> | ||
| 31 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs new file mode 100644 index 00000000..f8203a07 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 9 | </Feature> | ||
| 10 | |||
| 11 | <WixVariable Id="TestFile" Value="test2.txt" /> | ||
| 12 | </Package> | ||
| 13 | |||
| 14 | <Fragment> | ||
| 15 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 16 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 17 | </StandardDirectory> | ||
| 18 | </Fragment> | ||
| 19 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs new file mode 100644 index 00000000..df867923 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <Binary Id="Test.txt" SourceFile="!(wix.TestFile=test.txt)" /> | ||
| 5 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 6 | <Component> | ||
| 7 | <File Source="test.txt" /> | ||
| 8 | </Component> | ||
| 9 | <Component Id="Shared.dll" Shared="yes"> | ||
| 10 | <File Name="Shared.dll" Source="test.txt" /> | ||
| 11 | </Component> | ||
| 12 | </ComponentGroup> | ||
| 13 | </Fragment> | ||
| 14 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt new file mode 100644 index 00000000..eab3a9b5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test2.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs new file mode 100644 index 00000000..7e6eee9f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <CustomAction Id="CAFromExtension" DllEntry="DoesntExist" BinaryRef="BinFromWir" /> | ||
| 8 | |||
| 9 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 10 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 11 | </Feature> | ||
| 12 | </Package> | ||
| 13 | |||
| 14 | <Fragment> | ||
| 15 | <StandardDirectory Id="ProgramFilesFolder"> | ||
| 16 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 17 | </StandardDirectory> | ||
| 18 | </Fragment> | ||
| 19 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 5 | <Component> | ||
| 6 | <File Source="test.txt" /> | ||
| 7 | </Component> | ||
| 8 | </ComponentGroup> | ||
| 9 | </Fragment> | ||
| 10 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | |||
| 3 | <!-- | ||
| 4 | This file contains the declaration of all the localizable strings. | ||
| 5 | --> | ||
| 6 | <WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US"> | ||
| 7 | |||
| 8 | <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String> | ||
| 9 | <String Id="FeatureTitle">MsiPackage</String> | ||
| 10 | |||
| 11 | </WixLocalization> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs new file mode 100644 index 00000000..b29a785f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 2 | <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine"> | ||
| 3 | |||
| 4 | |||
| 5 | <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" /> | ||
| 6 | |||
| 7 | <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)"> | ||
| 8 | <ComponentGroupRef Id="ProductComponents" /> | ||
| 9 | </Feature> | ||
| 10 | </Package> | ||
| 11 | |||
| 12 | <Fragment> | ||
| 13 | <Directory Id="TARGETDIR" Name="SourceDir"> | ||
| 14 | <Directory Id="ProgramFilesFolder"> | ||
| 15 | <Directory Id="INSTALLFOLDER" Name="MsiPackage" /> | ||
| 16 | </Directory> | ||
| 17 | </Directory> | ||
| 18 | </Fragment> | ||
| 19 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs new file mode 100644 index 00000000..7d1a4ae1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"> | ||
| 3 | <Fragment> | ||
| 4 | <Binary Id="FooAlpha" SourceFile="!(bindpath.AlphaBits)foo.dll" /> | ||
| 5 | </Fragment> | ||
| 6 | |||
| 7 | <Fragment> | ||
| 8 | <Binary Id="FooMips" SourceFile="!(bindpath.MipsBits)foo.dll" /> | ||
| 9 | </Fragment> | ||
| 10 | |||
| 11 | <Fragment> | ||
| 12 | <Binary Id="FooPowerPC" SourceFile="!(bindpath.PowerBits)foo.dll" /> | ||
| 13 | </Fragment> | ||
| 14 | |||
| 15 | <Fragment> | ||
| 16 | <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> | ||
| 17 | <Component> | ||
| 18 | <File Source="test.txt" /> | ||
| 19 | </Component> | ||
| 20 | |||
| 21 | <Component Id="Shared.dll" Shared="yes"> | ||
| 22 | <File Name="Shared.dll" Source="test.txt" /> | ||
| 23 | </Component> | ||
| 24 | </ComponentGroup> | ||
| 25 | </Fragment> | ||
| 26 | </Wix> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll new file mode 100644 index 00000000..fd36c768 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll | |||
| @@ -0,0 +1 @@ | |||
| This is alpha\foo.dll. | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll new file mode 100644 index 00000000..292925c7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll | |||
| @@ -0,0 +1 @@ | |||
| This is mips\foo.dll. | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll new file mode 100644 index 00000000..663e9d99 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll | |||
| @@ -0,0 +1 @@ | |||
| This is powerpc\foo.dll. | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt | |||
| @@ -0,0 +1 @@ | |||
| This is test.txt. \ No newline at end of file | |||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs new file mode 100644 index 00000000..5330305e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Core.TestPackage; | ||
| 7 | using Xunit; | ||
| 8 | |||
| 9 | public class TestXmlFixture | ||
| 10 | { | ||
| 11 | [Fact] | ||
| 12 | public void ChangesIgnoredAttributesToStarToHelpMakeTestsLessFragile() | ||
| 13 | { | ||
| 14 | var original = @"<Top One='f'> | ||
| 15 | <First Two='t'> | ||
| 16 | <Target One='a' Two='b' Three='c' /> | ||
| 17 | </First> | ||
| 18 | <Target One='z' Two='x' Three = 'y' /> | ||
| 19 | </Top>"; | ||
| 20 | var expected = "<Top One='f'><First Two='t'><Target One='*' Two='*' Three='c' /></First><Target One='*' Two='*' Three='y' /></Top>"; | ||
| 21 | var ignored = new Dictionary<string, List<string>> { { "Target", new List<string> { "One", "Two", "Missing" } } }; | ||
| 22 | Assert.Equal(expected, original.GetTestXml(ignored)); | ||
| 23 | } | ||
| 24 | |||
| 25 | [Fact] | ||
| 26 | public void OutputsSingleQuotesSinceDoubleQuotesInCsharpLiteralStringsArePainful() | ||
| 27 | { | ||
| 28 | var original = "<Test Simple=\"\" EscapedDoubleQuote=\""\" SingleQuoteValue=\"'test'\" Alternating='\"' AlternatingEscaped='"' />"; | ||
| 29 | var expected = "<Test Simple='' EscapedDoubleQuote='\"' SingleQuoteValue=''test'' Alternating='\"' AlternatingEscaped='\"' />"; | ||
| 30 | Assert.Equal(expected, original.GetTestXml()); | ||
| 31 | } | ||
| 32 | |||
| 33 | [Fact] | ||
| 34 | public void RemovesAllNamespacesToReduceTyping() | ||
| 35 | { | ||
| 36 | var original = "<Test xmlns='a'><Child xmlns:b='b'><Grandchild xmlns:c='c' /><Grandchild /></Child></Test>"; | ||
| 37 | var expected = "<Test><Child><Grandchild /><Grandchild /></Child></Test>"; | ||
| 38 | Assert.Equal(expected, original.GetTestXml()); | ||
| 39 | } | ||
| 40 | |||
| 41 | [Fact] | ||
| 42 | public void RemovesUnnecessaryWhitespaceToAvoidLineEndingIssues() | ||
| 43 | { | ||
| 44 | var original = @"<Test> | ||
| 45 | <Child> | ||
| 46 | <Grandchild /> | ||
| 47 | <Grandchild /> | ||
| 48 | </Child> | ||
| 49 | </Test>"; | ||
| 50 | var expected = "<Test><Child><Grandchild /><Grandchild /></Child></Test>"; | ||
| 51 | Assert.Equal(expected, original.GetTestXml()); | ||
| 52 | } | ||
| 53 | |||
| 54 | [Fact] | ||
| 55 | public void RemovesXmlDeclarationToReduceTyping() | ||
| 56 | { | ||
| 57 | var original = "<?xml version='1.0'?><Test />"; | ||
| 58 | var expected = "<Test />"; | ||
| 59 | Assert.Equal(expected, original.GetTestXml()); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs new file mode 100644 index 00000000..15e5d334 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | |||
| 2 | // 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. | ||
| 3 | |||
| 4 | namespace WixToolsetTest.CoreIntegration | ||
| 5 | { | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Core; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using WixToolset.Data.Bind; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | using Xunit; | ||
| 12 | |||
| 13 | public class VariableResolverFixture | ||
| 14 | { | ||
| 15 | [Fact] | ||
| 16 | public void CanRecursivelyResolveVariables() | ||
| 17 | { | ||
| 18 | var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); | ||
| 19 | var variableResolver = serviceProvider.GetService<IVariableResolver>(); | ||
| 20 | |||
| 21 | var variables = new Dictionary<string, BindVariable>() | ||
| 22 | { | ||
| 23 | { "ProductName", new BindVariable() { Id = "ProductName", Value = "Localized Product Name" } }, | ||
| 24 | { "ProductNameEdition", new BindVariable() { Id = "ProductNameEdition", Value = "!(loc.ProductName) Enterprise Edition" } }, | ||
| 25 | { "ProductNameEditionVersion", new BindVariable() { Id = "ProductNameEditionVersion", Value = "!(loc.ProductNameEdition) v1.2.3" } }, | ||
| 26 | }; | ||
| 27 | |||
| 28 | var localization = new Localization(0, null, "x-none", variables, new Dictionary<string,LocalizedControl>()); | ||
| 29 | |||
| 30 | variableResolver.AddLocalization(localization); | ||
| 31 | |||
| 32 | var result = variableResolver.ResolveVariables(null, "These are not the loc strings you're looking for."); | ||
| 33 | Assert.Equal("These are not the loc strings you're looking for.", result.Value); | ||
| 34 | Assert.False(result.UpdatedValue); | ||
| 35 | |||
| 36 | result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductName)"); | ||
| 37 | Assert.Equal("Welcome to Localized Product Name", result.Value); | ||
| 38 | Assert.True(result.UpdatedValue); | ||
| 39 | |||
| 40 | result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEdition)"); | ||
| 41 | Assert.Equal("Welcome to Localized Product Name Enterprise Edition", result.Value); | ||
| 42 | Assert.True(result.UpdatedValue); | ||
| 43 | |||
| 44 | result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion)"); | ||
| 45 | Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3", result.Value); | ||
| 46 | Assert.True(result.UpdatedValue); | ||
| 47 | |||
| 48 | result = variableResolver.ResolveVariables(null, "Welcome to !(bind.property.ProductVersion)"); | ||
| 49 | Assert.Equal("Welcome to !(bind.property.ProductVersion)", result.Value); | ||
| 50 | Assert.False(result.UpdatedValue); | ||
| 51 | Assert.True(result.DelayedResolve); | ||
| 52 | |||
| 53 | var withUnknownLocString = "Welcome to !(loc.UnknownLocalizationVariable)"; | ||
| 54 | Assert.Throws<WixException>(() => variableResolver.ResolveVariables(null, withUnknownLocString)); | ||
| 55 | |||
| 56 | result = variableResolver.ResolveVariables(null, withUnknownLocString, errorOnUnknown: false); | ||
| 57 | Assert.Equal(withUnknownLocString, result.Value); | ||
| 58 | Assert.False(result.UpdatedValue); | ||
| 59 | |||
| 60 | result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable)"); | ||
| 61 | Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable)", result.Value); | ||
| 62 | Assert.True(result.UpdatedValue); | ||
| 63 | |||
| 64 | result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); | ||
| 65 | Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); | ||
| 66 | Assert.True(result.UpdatedValue); | ||
| 67 | Assert.True(result.DelayedResolve); | ||
| 68 | |||
| 69 | result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion) !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); | ||
| 70 | Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3 !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); | ||
| 71 | Assert.True(result.UpdatedValue); | ||
| 72 | Assert.True(result.DelayedResolve); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs new file mode 100644 index 00000000..c5b6c261 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs | |||
| @@ -0,0 +1,63 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixBuildTools.TestSupport; | ||
| 7 | using WixToolset.Core.TestPackage; | ||
| 8 | using WixToolset.Data; | ||
| 9 | using Xunit; | ||
| 10 | |||
| 11 | public class WarningFixture | ||
| 12 | { | ||
| 13 | [Fact] | ||
| 14 | public void SuppressedWarningsWithWarningAsErrorsAreNotErrors() | ||
| 15 | { | ||
| 16 | var folder = TestData.Get(@"TestData\Payload"); | ||
| 17 | |||
| 18 | using (var fs = new DisposableFileSystem()) | ||
| 19 | { | ||
| 20 | var baseFolder = fs.GetFolder(); | ||
| 21 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 22 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 23 | |||
| 24 | var result = WixRunner.Execute(warningsAsErrors: true, new[] | ||
| 25 | { | ||
| 26 | "build", | ||
| 27 | "-sw1152", | ||
| 28 | Path.Combine(folder, "CanonicalizeName.wxs"), | ||
| 29 | "-intermediateFolder", intermediateFolder, | ||
| 30 | "-o", wixlibPath, | ||
| 31 | }); | ||
| 32 | |||
| 33 | result.AssertSuccess(); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | [Fact] | ||
| 38 | public void WarningsAsErrorsTreatsWarningsAsErrors() | ||
| 39 | { | ||
| 40 | var folder = TestData.Get(@"TestData\Payload"); | ||
| 41 | |||
| 42 | using (var fs = new DisposableFileSystem()) | ||
| 43 | { | ||
| 44 | var baseFolder = fs.GetFolder(); | ||
| 45 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 46 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 47 | |||
| 48 | var result = WixRunner.Execute(warningsAsErrors: true, new[] | ||
| 49 | { | ||
| 50 | "build", | ||
| 51 | Path.Combine(folder, "CanonicalizeName.wxs"), | ||
| 52 | "-intermediateFolder", intermediateFolder, | ||
| 53 | "-o", wixlibPath, | ||
| 54 | }); | ||
| 55 | |||
| 56 | Assert.Equal((int)WarningMessages.Ids.PathCanonicalized, result.ExitCode); | ||
| 57 | |||
| 58 | var message = Assert.Single(result.Messages); | ||
| 59 | Assert.Equal(MessageLevel.Warning, message.Level); // TODO: is this right? | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj new file mode 100644 index 00000000..fc62e932 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- 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. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFramework>netcoreapp3.1</TargetFramework> | ||
| 7 | <IsPackable>false</IsPackable> | ||
| 8 | <DebugType>embedded</DebugType> | ||
| 9 | </PropertyGroup> | ||
| 10 | |||
| 11 | <ItemGroup> | ||
| 12 | <Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" /> | ||
| 13 | </ItemGroup> | ||
| 14 | |||
| 15 | <ItemGroup> | ||
| 16 | <ProjectReference Include="..\..\WixToolset.Core\WixToolset.Core.csproj" /> | ||
| 17 | <ProjectReference Include="..\..\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj" /> | ||
| 18 | <ProjectReference Include="..\..\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj" /> | ||
| 19 | <ProjectReference Include="..\..\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" /> | ||
| 20 | <ProjectReference Include="..\Example.Extension\Example.Extension.csproj" /> | ||
| 21 | </ItemGroup> | ||
| 22 | |||
| 23 | <ItemGroup> | ||
| 24 | <PackageReference Include="WixBuildTools.TestSupport" Version="4.0.*" /> | ||
| 25 | </ItemGroup> | ||
| 26 | |||
| 27 | <ItemGroup> | ||
| 28 | <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" /> | ||
| 29 | <PackageReference Include="xunit" Version="2.4.1" /> | ||
| 30 | <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" PrivateAssets="All" /> | ||
| 31 | </ItemGroup> | ||
| 32 | </Project> | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs new file mode 100644 index 00000000..942f253f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs | |||
| @@ -0,0 +1,205 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using WixBuildTools.TestSupport; | ||
| 9 | using WixToolset.Core.TestPackage; | ||
| 10 | using WixToolset.Data; | ||
| 11 | using WixToolset.Data.Symbols; | ||
| 12 | using Example.Extension; | ||
| 13 | using Xunit; | ||
| 14 | |||
| 15 | public class WixiplFixture | ||
| 16 | { | ||
| 17 | [Fact] | ||
| 18 | public void CanBuildSingleFile() | ||
| 19 | { | ||
| 20 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 21 | |||
| 22 | using (var fs = new DisposableFileSystem()) | ||
| 23 | { | ||
| 24 | var baseFolder = fs.GetFolder(); | ||
| 25 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 26 | var wixiplPath = Path.Combine(intermediateFolder, @"test.wixipl"); | ||
| 27 | |||
| 28 | var result = WixRunner.Execute(new[] | ||
| 29 | { | ||
| 30 | "build", | ||
| 31 | Path.Combine(folder, "Package.wxs"), | ||
| 32 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 33 | "-intermediateFolder", intermediateFolder, | ||
| 34 | "-o", wixiplPath, | ||
| 35 | }); | ||
| 36 | |||
| 37 | result.AssertSuccess(); | ||
| 38 | |||
| 39 | var intermediate = Intermediate.Load(wixiplPath); | ||
| 40 | |||
| 41 | Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); | ||
| 42 | Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); | ||
| 43 | Assert.False(intermediate.HasLevel(IntermediateLevels.Resolved)); | ||
| 44 | |||
| 45 | result = WixRunner.Execute(new[] | ||
| 46 | { | ||
| 47 | "build", | ||
| 48 | Path.Combine(intermediateFolder, @"test.wixipl"), | ||
| 49 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 50 | "-bindpath", Path.Combine(folder, "data"), | ||
| 51 | "-intermediateFolder", intermediateFolder, | ||
| 52 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 53 | }); | ||
| 54 | |||
| 55 | result.AssertSuccess(); | ||
| 56 | |||
| 57 | intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 58 | |||
| 59 | Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); | ||
| 60 | Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); | ||
| 61 | Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); | ||
| 62 | |||
| 63 | var section = intermediate.Sections.Single(); | ||
| 64 | |||
| 65 | var fileSymbol = section.Symbols.OfType<FileSymbol>().First(); | ||
| 66 | Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); | ||
| 67 | Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | [Fact] | ||
| 72 | public void CannotBuildWithSourceFileAndWixipl() | ||
| 73 | { | ||
| 74 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 75 | |||
| 76 | using (var fs = new DisposableFileSystem()) | ||
| 77 | { | ||
| 78 | var baseFolder = fs.GetFolder(); | ||
| 79 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 80 | |||
| 81 | var result = WixRunner.Execute(new[] | ||
| 82 | { | ||
| 83 | "build", | ||
| 84 | Path.Combine(folder, "Package.wxs"), | ||
| 85 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 86 | "-intermediateFolder", intermediateFolder, | ||
| 87 | "-o", Path.Combine(intermediateFolder, @"test.wixipl") | ||
| 88 | }); | ||
| 89 | |||
| 90 | result.AssertSuccess(); | ||
| 91 | |||
| 92 | result = WixRunner.Execute(new[] | ||
| 93 | { | ||
| 94 | "build", | ||
| 95 | Path.Combine(folder, "Package.wxs"), | ||
| 96 | Path.Combine(intermediateFolder, @"test.wixipl"), | ||
| 97 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 98 | "-bindpath", Path.Combine(folder, "data"), | ||
| 99 | "-intermediateFolder", intermediateFolder, | ||
| 100 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 101 | }); | ||
| 102 | Assert.Equal((int)ErrorMessages.Ids.WixiplSourceFileIsExclusive, result.ExitCode); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | [Fact] | ||
| 107 | public void CanBuildMsiUsingExtensionLibrary() | ||
| 108 | { | ||
| 109 | var folder = TestData.Get(@"TestData\Wixipl"); | ||
| 110 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 111 | |||
| 112 | using (var fs = new DisposableFileSystem()) | ||
| 113 | { | ||
| 114 | var baseFolder = fs.GetFolder(); | ||
| 115 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 116 | |||
| 117 | var result = WixRunner.Execute(new[] | ||
| 118 | { | ||
| 119 | "build", | ||
| 120 | "-ext", extensionPath, | ||
| 121 | Path.Combine(folder, "Package.wxs"), | ||
| 122 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 123 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 124 | "-bindpath", Path.Combine(folder, "data"), | ||
| 125 | "-intermediateFolder", intermediateFolder, | ||
| 126 | "-o", Path.Combine(baseFolder, @"bin\test.msi"), | ||
| 127 | }); | ||
| 128 | |||
| 129 | result.AssertSuccess(); | ||
| 130 | |||
| 131 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 132 | var section = intermediate.Sections.Single(); | ||
| 133 | |||
| 134 | { | ||
| 135 | var fileSymbol = section.Symbols.OfType<FileSymbol>().Single(); | ||
| 136 | Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); | ||
| 137 | Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 138 | } | ||
| 139 | |||
| 140 | { | ||
| 141 | var binary = section.Symbols.OfType<BinarySymbol>().Single(); | ||
| 142 | var path = binary[BinarySymbolFields.Data].AsPath().Path; | ||
| 143 | Assert.StartsWith(Path.Combine(baseFolder, @"obj\Example.Extension"), path); | ||
| 144 | Assert.EndsWith(@"wix-ir\example.txt", path); | ||
| 145 | Assert.Equal(@"BinFromWir", binary.Id.Id); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | [Fact] | ||
| 151 | public void CanBuildWixiplUsingExtensionLibrary() | ||
| 152 | { | ||
| 153 | var folder = TestData.Get(@"TestData\Wixipl"); | ||
| 154 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 155 | |||
| 156 | using (var fs = new DisposableFileSystem()) | ||
| 157 | { | ||
| 158 | var baseFolder = fs.GetFolder(); | ||
| 159 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 160 | |||
| 161 | var result = WixRunner.Execute(new[] | ||
| 162 | { | ||
| 163 | "build", | ||
| 164 | "-ext", extensionPath, | ||
| 165 | Path.Combine(folder, "Package.wxs"), | ||
| 166 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 167 | "-intermediateFolder", intermediateFolder, | ||
| 168 | "-o", Path.Combine(intermediateFolder, @"test.wixipl"), | ||
| 169 | }); | ||
| 170 | |||
| 171 | result.AssertSuccess(); | ||
| 172 | |||
| 173 | result = WixRunner.Execute(new[] | ||
| 174 | { | ||
| 175 | "build", | ||
| 176 | Path.Combine(intermediateFolder, @"test.wixipl"), | ||
| 177 | "-ext", extensionPath, | ||
| 178 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 179 | "-bindpath", Path.Combine(folder, "data"), | ||
| 180 | "-intermediateFolder", intermediateFolder, | ||
| 181 | "-o", Path.Combine(baseFolder, @"bin\test.msi"), | ||
| 182 | }); | ||
| 183 | |||
| 184 | result.AssertSuccess(); | ||
| 185 | |||
| 186 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 187 | var section = intermediate.Sections.Single(); | ||
| 188 | |||
| 189 | { | ||
| 190 | var fileSymbol = section.Symbols.OfType<FileSymbol>().Single(); | ||
| 191 | Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); | ||
| 192 | Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 193 | } | ||
| 194 | |||
| 195 | { | ||
| 196 | var binary = section.Symbols.OfType<BinarySymbol>().Single(); | ||
| 197 | var path = binary[BinarySymbolFields.Data].AsPath().Path; | ||
| 198 | Assert.StartsWith(Path.Combine(baseFolder, @"obj\test"), path); | ||
| 199 | Assert.EndsWith(@"wix-ir\example.txt", path); | ||
| 200 | Assert.Equal(@"BinFromWir", binary.Id.Id); | ||
| 201 | } | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs new file mode 100644 index 00000000..d7296cfe --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs | |||
| @@ -0,0 +1,316 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using System.Linq; | ||
| 8 | using Example.Extension; | ||
| 9 | using WixBuildTools.TestSupport; | ||
| 10 | using WixToolset.Core.TestPackage; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Symbols; | ||
| 13 | using Xunit; | ||
| 14 | |||
| 15 | public class WixlibFixture | ||
| 16 | { | ||
| 17 | [Fact] | ||
| 18 | public void CanBuildSimpleBundleUsingWixlib() | ||
| 19 | { | ||
| 20 | var folder = TestData.Get(@"TestData\SimpleBundle"); | ||
| 21 | |||
| 22 | using (var fs = new DisposableFileSystem()) | ||
| 23 | { | ||
| 24 | var baseFolder = fs.GetFolder(); | ||
| 25 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 26 | |||
| 27 | var result = WixRunner.Execute(new[] | ||
| 28 | { | ||
| 29 | "build", | ||
| 30 | Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), | ||
| 31 | "-intermediateFolder", intermediateFolder, | ||
| 32 | "-o", Path.Combine(intermediateFolder, @"test.wixlib") | ||
| 33 | }); | ||
| 34 | |||
| 35 | result.AssertSuccess(); | ||
| 36 | |||
| 37 | result = WixRunner.Execute(new[] | ||
| 38 | { | ||
| 39 | "build", | ||
| 40 | Path.Combine(folder, "MultiFileBundle.wxs"), | ||
| 41 | "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), | ||
| 42 | "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), | ||
| 43 | "-bindpath", Path.Combine(folder, "data"), | ||
| 44 | "-intermediateFolder", intermediateFolder, | ||
| 45 | "-o", Path.Combine(baseFolder, @"bin\test.exe") | ||
| 46 | }); | ||
| 47 | |||
| 48 | result.AssertSuccess(); | ||
| 49 | |||
| 50 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); | ||
| 51 | Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | [Fact] | ||
| 56 | public void CanBuildWixlibWithBinariesFromNamedBindPaths() | ||
| 57 | { | ||
| 58 | var folder = TestData.Get(@"TestData\WixlibWithBinaries"); | ||
| 59 | |||
| 60 | using (var fs = new DisposableFileSystem()) | ||
| 61 | { | ||
| 62 | var baseFolder = fs.GetFolder(); | ||
| 63 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 64 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 65 | |||
| 66 | var result = WixRunner.Execute(new[] | ||
| 67 | { | ||
| 68 | "build", | ||
| 69 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 70 | "-bf", | ||
| 71 | "-bindpath", Path.Combine(folder, "data"), | ||
| 72 | // Use names that aren't excluded in default .gitignores. | ||
| 73 | "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", | ||
| 74 | "-bindpath", $"MipsBits={Path.Combine(folder, "data", "mips")}", | ||
| 75 | "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", | ||
| 76 | "-intermediateFolder", intermediateFolder, | ||
| 77 | "-o", wixlibPath, | ||
| 78 | }); | ||
| 79 | |||
| 80 | result.AssertSuccess(); | ||
| 81 | |||
| 82 | var wixlib = Intermediate.Load(wixlibPath); | ||
| 83 | var binarySymbols = wixlib.Sections.SelectMany(s => s.Symbols).OfType<BinarySymbol>().ToList(); | ||
| 84 | Assert.Equal(3, binarySymbols.Count); | ||
| 85 | Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll")); | ||
| 86 | Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-1")); | ||
| 87 | Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-2")); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | [Fact] | ||
| 92 | public void CanBuildSingleFileUsingWixlib() | ||
| 93 | { | ||
| 94 | var folder = TestData.Get(@"TestData\SingleFile"); | ||
| 95 | |||
| 96 | using (var fs = new DisposableFileSystem()) | ||
| 97 | { | ||
| 98 | var baseFolder = fs.GetFolder(); | ||
| 99 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 100 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 101 | |||
| 102 | var result = WixRunner.Execute(new[] | ||
| 103 | { | ||
| 104 | "build", | ||
| 105 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 106 | "-intermediateFolder", intermediateFolder, | ||
| 107 | "-o", wixlibPath, | ||
| 108 | }); | ||
| 109 | |||
| 110 | result.AssertSuccess(); | ||
| 111 | |||
| 112 | var wixlib = Intermediate.Load(wixlibPath); | ||
| 113 | |||
| 114 | Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); | ||
| 115 | Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); | ||
| 116 | Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); | ||
| 117 | Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); | ||
| 118 | |||
| 119 | result = WixRunner.Execute(new[] | ||
| 120 | { | ||
| 121 | "build", | ||
| 122 | Path.Combine(folder, "Package.wxs"), | ||
| 123 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 124 | "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), | ||
| 125 | "-bindpath", Path.Combine(folder, "data"), | ||
| 126 | "-intermediateFolder", intermediateFolder, | ||
| 127 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 128 | }); | ||
| 129 | |||
| 130 | result.AssertSuccess(); | ||
| 131 | |||
| 132 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 133 | |||
| 134 | Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); | ||
| 135 | Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); | ||
| 136 | Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); | ||
| 137 | Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); | ||
| 138 | |||
| 139 | var section = intermediate.Sections.Single(); | ||
| 140 | |||
| 141 | var wixFile = section.Symbols.OfType<FileSymbol>().First(); | ||
| 142 | Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[FileSymbolFields.Source].AsPath().Path); | ||
| 143 | Assert.Equal(@"test.txt", wixFile[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | [Fact] | ||
| 148 | public void CanOverridePathWixVariable() | ||
| 149 | { | ||
| 150 | var folder = TestData.Get(@"TestData\WixVariableOverride"); | ||
| 151 | |||
| 152 | using (var fs = new DisposableFileSystem()) | ||
| 153 | { | ||
| 154 | var baseFolder = fs.GetFolder(); | ||
| 155 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 156 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 157 | |||
| 158 | var result = WixRunner.Execute(new[] | ||
| 159 | { | ||
| 160 | "build", | ||
| 161 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 162 | "-bf", | ||
| 163 | "-bindpath", Path.Combine(folder, "data"), | ||
| 164 | "-intermediateFolder", intermediateFolder, | ||
| 165 | "-o", wixlibPath, | ||
| 166 | }); | ||
| 167 | |||
| 168 | result.AssertSuccess(); | ||
| 169 | |||
| 170 | var wixlib = Intermediate.Load(wixlibPath); | ||
| 171 | |||
| 172 | Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); | ||
| 173 | Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); | ||
| 174 | Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); | ||
| 175 | Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); | ||
| 176 | |||
| 177 | result = WixRunner.Execute(new[] | ||
| 178 | { | ||
| 179 | "build", | ||
| 180 | Path.Combine(folder, "Package.wxs"), | ||
| 181 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 182 | "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), | ||
| 183 | "-bindpath", Path.Combine(folder, "data"), | ||
| 184 | "-intermediateFolder", intermediateFolder, | ||
| 185 | "-o", Path.Combine(baseFolder, @"bin\test.msi") | ||
| 186 | }); | ||
| 187 | |||
| 188 | result.AssertSuccess(); | ||
| 189 | |||
| 190 | var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); | ||
| 191 | |||
| 192 | Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); | ||
| 193 | Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); | ||
| 194 | Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); | ||
| 195 | Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); | ||
| 196 | |||
| 197 | var section = intermediate.Sections.Single(); | ||
| 198 | |||
| 199 | var wixFile = section.Symbols.OfType<BinarySymbol>().First(); | ||
| 200 | Assert.Equal(Path.Combine(folder, @"data\test2.txt"), wixFile.Data.Path); | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | [Fact] | ||
| 205 | public void CanBuildWithExtensionUsingWixlib() | ||
| 206 | { | ||
| 207 | var folder = TestData.Get(@"TestData\ExampleExtension"); | ||
| 208 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 209 | |||
| 210 | using (var fs = new DisposableFileSystem()) | ||
| 211 | { | ||
| 212 | var baseFolder = fs.GetFolder(); | ||
| 213 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 214 | |||
| 215 | var result = WixRunner.Execute(new[] | ||
| 216 | { | ||
| 217 | "build", | ||
| 218 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 219 | "-ext", extensionPath, | ||
| 220 | "-intermediateFolder", intermediateFolder, | ||
| 221 | "-o", Path.Combine(intermediateFolder, @"test.wixlib") | ||
| 222 | }); | ||
| 223 | |||
| 224 | result.AssertSuccess(); | ||
| 225 | |||
| 226 | result = WixRunner.Execute(new[] | ||
| 227 | { | ||
| 228 | "build", | ||
| 229 | Path.Combine(folder, "Package.wxs"), | ||
| 230 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 231 | "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), | ||
| 232 | "-ext", extensionPath, | ||
| 233 | "-bindpath", Path.Combine(folder, "data"), | ||
| 234 | "-intermediateFolder", intermediateFolder, | ||
| 235 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 236 | }); | ||
| 237 | |||
| 238 | result.AssertSuccess(); | ||
| 239 | |||
| 240 | var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); | ||
| 241 | var section = intermediate.Sections.Single(); | ||
| 242 | |||
| 243 | var fileSymbol = section.Symbols.OfType<FileSymbol>().Single(); | ||
| 244 | Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); | ||
| 245 | Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 246 | |||
| 247 | var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); | ||
| 248 | Assert.Equal("Foo", example.Id?.Id); | ||
| 249 | Assert.Equal("Bar", example[0].AsString()); | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | [Fact] | ||
| 254 | public void CanBuildWithExtensionUsingMultipleWixlibs() | ||
| 255 | { | ||
| 256 | var folder = TestData.Get(@"TestData\ComplexExampleExtension"); | ||
| 257 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 258 | |||
| 259 | using (var fs = new DisposableFileSystem()) | ||
| 260 | { | ||
| 261 | var baseFolder = fs.GetFolder(); | ||
| 262 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 263 | |||
| 264 | var result = WixRunner.Execute(new[] | ||
| 265 | { | ||
| 266 | "build", | ||
| 267 | Path.Combine(folder, "PackageComponents.wxs"), | ||
| 268 | "-ext", extensionPath, | ||
| 269 | "-intermediateFolder", intermediateFolder, | ||
| 270 | "-o", Path.Combine(intermediateFolder, @"components.wixlib") | ||
| 271 | }); | ||
| 272 | |||
| 273 | result.AssertSuccess(); | ||
| 274 | |||
| 275 | result = WixRunner.Execute(new[] | ||
| 276 | { | ||
| 277 | "build", | ||
| 278 | Path.Combine(folder, "OtherComponents.wxs"), | ||
| 279 | "-ext", extensionPath, | ||
| 280 | "-intermediateFolder", intermediateFolder, | ||
| 281 | "-o", Path.Combine(intermediateFolder, @"other.wixlib") | ||
| 282 | }); | ||
| 283 | |||
| 284 | result.AssertSuccess(); | ||
| 285 | |||
| 286 | result = WixRunner.Execute(new[] | ||
| 287 | { | ||
| 288 | "build", | ||
| 289 | Path.Combine(folder, "Package.wxs"), | ||
| 290 | "-loc", Path.Combine(folder, "Package.en-us.wxl"), | ||
| 291 | "-lib", Path.Combine(intermediateFolder, @"components.wixlib"), | ||
| 292 | "-lib", Path.Combine(intermediateFolder, @"other.wixlib"), | ||
| 293 | "-ext", extensionPath, | ||
| 294 | "-bindpath", Path.Combine(folder, "data"), | ||
| 295 | "-intermediateFolder", intermediateFolder, | ||
| 296 | "-o", Path.Combine(intermediateFolder, @"bin\test.msi") | ||
| 297 | }); | ||
| 298 | |||
| 299 | result.AssertSuccess(); | ||
| 300 | |||
| 301 | var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); | ||
| 302 | var section = intermediate.Sections.Single(); | ||
| 303 | |||
| 304 | var fileSymbols = section.Symbols.OfType<FileSymbol>().OrderBy(t => Path.GetFileName(t.Source.Path)).ToArray(); | ||
| 305 | Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbols[0][FileSymbolFields.Source].AsPath().Path); | ||
| 306 | Assert.Equal(@"example.txt", fileSymbols[0][FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 307 | Assert.Equal(Path.Combine(folder, @"data\other.txt"), fileSymbols[1][FileSymbolFields.Source].AsPath().Path); | ||
| 308 | Assert.Equal(@"other.txt", fileSymbols[1][FileSymbolFields.Source].PreviousValue.AsPath().Path); | ||
| 309 | |||
| 310 | var examples = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).ToArray(); | ||
| 311 | Assert.Equal(new string[] { "Foo", "Other" }, examples.Select(t => t.Id?.Id).ToArray()); | ||
| 312 | Assert.Equal(new[] { "Bar", "Value" }, examples.Select(t => t[0].AsString()).ToArray()); | ||
| 313 | } | ||
| 314 | } | ||
| 315 | } | ||
| 316 | } | ||
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs new file mode 100644 index 00000000..57351b27 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | // 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. | ||
| 2 | |||
| 3 | namespace WixToolsetTest.CoreIntegration | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using System.Linq; | ||
| 7 | using WixBuildTools.TestSupport; | ||
| 8 | using WixToolset.Core.TestPackage; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Symbols; | ||
| 11 | using Xunit; | ||
| 12 | |||
| 13 | public class WixlibQueryFixture | ||
| 14 | { | ||
| 15 | [Fact] | ||
| 16 | public void UpgradeProducesReferenceToRemoveExistingProducts() | ||
| 17 | { | ||
| 18 | var folder = TestData.Get(@"TestData\Upgrade"); | ||
| 19 | |||
| 20 | using (var fs = new DisposableFileSystem()) | ||
| 21 | { | ||
| 22 | var baseFolder = fs.GetFolder(); | ||
| 23 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 24 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 25 | |||
| 26 | var result = WixRunner.Execute(new[] | ||
| 27 | { | ||
| 28 | "build", | ||
| 29 | Path.Combine(folder, "DetectOnly.wxs"), | ||
| 30 | "-intermediateFolder", intermediateFolder, | ||
| 31 | "-o", wixlibPath, | ||
| 32 | }); | ||
| 33 | |||
| 34 | result.AssertSuccess(); | ||
| 35 | |||
| 36 | var intermediate = Intermediate.Load(wixlibPath); | ||
| 37 | var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); | ||
| 38 | var wixSimpleRefSymbols = allSymbols.OfType<WixSimpleReferenceSymbol>(); | ||
| 39 | var repRef = wixSimpleRefSymbols.Where(t => t.Table == "WixAction" && | ||
| 40 | t.PrimaryKeys == "InstallExecuteSequence/RemoveExistingProducts") | ||
| 41 | .SingleOrDefault(); | ||
| 42 | Assert.NotNull(repRef); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | [Fact] | ||
| 47 | public void TypeLibLanguageAsStringReturnsZero() | ||
| 48 | { | ||
| 49 | var folder = TestData.Get(@"TestData\TypeLib"); | ||
| 50 | |||
| 51 | using (var fs = new DisposableFileSystem()) | ||
| 52 | { | ||
| 53 | var baseFolder = fs.GetFolder(); | ||
| 54 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 55 | var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); | ||
| 56 | |||
| 57 | var result = WixRunner.Execute(new[] | ||
| 58 | { | ||
| 59 | "build", | ||
| 60 | Path.Combine(folder, "Language0.wxs"), | ||
| 61 | "-intermediateFolder", intermediateFolder, | ||
| 62 | "-o", wixlibPath | ||
| 63 | }); | ||
| 64 | |||
| 65 | result.AssertSuccess(); | ||
| 66 | |||
| 67 | var intermediate = Intermediate.Load(wixlibPath); | ||
| 68 | var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); | ||
| 69 | var typeLibSymbol = allSymbols.OfType<TypeLibSymbol>() | ||
| 70 | .SingleOrDefault(); | ||
| 71 | Assert.NotNull(typeLibSymbol); | ||
| 72 | |||
| 73 | var fields = typeLibSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool | ||
| 74 | ? field.AsNullableNumber()?.ToString() | ||
| 75 | : field?.AsString()) | ||
| 76 | .ToList(); | ||
| 77 | Assert.Equal("0", fields[1]); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
