diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 3877548419e..3d4690384c3 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -106,6 +106,22 @@ jobs: run: | python ./ci_tools/create_signing_metadata.py "${{ env.DMDF }}" "${{ env.AZURE_ENDPOINT }}" "${{ env.AZURE_CODE_SIGNING_NAME }}" "${{ env.AZURE_CERT_PROFILE_NAME }}" + - name: Install MsiVal2 and Orca tools + if: success() && matrix.type == 'msbuild' && !startsWith(github.ref, 'refs/tags/') + shell: powershell + run: | + md -Force ./temp + cp 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\MsiVal2-x86_en-us.msi' ./temp/ + cp 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\8e4755178e6b5bcba8d3538c3630b7a5.cab' ./temp/ + cp 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\b80b7321357f7c9f281536f9440dfe68.cab' ./temp/ + cp 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\c2aabf6ea5c1d348ec22f3aeb92f8656.cab' ./temp/ + cp 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\Orca-x86_en-us.msi' ./temp/ + cp 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\838060235bcd28bf40ef7532c50ee032.cab' ./temp/ + cp 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\a35cd6c9233b6ba3da66eecaa9190436.cab' ./temp/ + cp 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\fe38b2fd0d440e3c6740b626f51a22fc.cab' ./temp/ + Start-Process "msiexec.exe" -ArgumentList "/i temp\MsiVal2-x86_en-us.msi" -Wait + Start-Process "msiexec.exe" -ArgumentList "/i temp\Orca-x86_en-us.msi" -Wait + - name: Build if: success() && matrix.type == 'msbuild' run: msbuild win_build\boinc.sln -p:Configuration=${{matrix.configuration}} -p:Platform=${{matrix.platform}} -p:VcpkgTripletConfig=ci -p:DLIB=${{ env.DLIB }} -p:DMDF=${{ env.DMDF }} -m @@ -119,6 +135,10 @@ jobs: working-directory: win_build\Build\${{matrix.platform}}\${{matrix.configuration}} run: ${{github.workspace}}\temp\OpenCppCoverage\OpenCppCoverage-x64\OpenCppCoverage.exe --cover_children --optimized_build --sources ${{github.workspace}} --export_type=cobertura:cobertura.xml -- unittests.exe --gtest_output=xml:gtest.xml + - name: Verify MSI file + if: success() && matrix.type == 'msbuild' && !startsWith(github.ref, 'refs/tags/') + run: python ./tests/msi_validation.py "C:\Program Files (x86)\MsiVal2\MsiVal2.exe" "${{github.workspace}}\win_build\Build\${{matrix.platform}}\${{matrix.configuration}}\boinc.msi" "${{github.workspace}}\installer\darice.cub" + - name: Run installation if: success() && matrix.platform == 'x64' && matrix.type == 'msbuild' shell: powershell diff --git a/ci_tools/source_code_check.py b/ci_tools/source_code_check.py index 0bd2ca88f5d..a75ec4f51de 100644 --- a/ci_tools/source_code_check.py +++ b/ci_tools/source_code_check.py @@ -97,6 +97,7 @@ def check(directory, bytes_to_check, exclude_dirs, exclude_extensions, exclude_f exclude_extensions = [ ".a", ".bmp", + ".cub", ".dll", ".exe", ".gif", diff --git a/ci_tools/trailing_whitespaces_check.py b/ci_tools/trailing_whitespaces_check.py index 88c668618dc..60dd58b6ccc 100644 --- a/ci_tools/trailing_whitespaces_check.py +++ b/ci_tools/trailing_whitespaces_check.py @@ -158,6 +158,7 @@ def check(directory, exclude_dirs, exclude_extensions, exclude_files, fix_errors exclude_extensions = [ ".a", ".bmp", + ".cub", ".dll", ".exe", ".gif", diff --git a/clientsetup/win/boinccas.cpp b/clientsetup/win/boinccas.cpp index efda5e0a943..d6044582ea6 100644 --- a/clientsetup/win/boinccas.cpp +++ b/clientsetup/win/boinccas.cpp @@ -1,6 +1,6 @@ // Berkeley Open Infrastructure for Network Computing // http://boinc.berkeley.edu -// Copyright (C) 2005 University of California +// Copyright (C) 2025 University of California // // This is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public @@ -841,6 +841,11 @@ UINT BOINCCABase::GetComponentKeyFilename( // Save the string strComponentKeyFilename = szBuffer; + // strComponentKeyFilename format is [strComponentName]_[filename] + // Remove the component name from the string + strComponentKeyFilename = + strComponentKeyFilename.substr(strComponentName.size() + 1); + strMessage = _T("The key filename for component '"); strMessage += strComponentName; strMessage += _T("' is '"); diff --git a/installer/ActionText.cpp b/installer/ActionText.cpp index 1f87905dc64..03727a4744b 100644 --- a/installer/ActionText.cpp +++ b/installer/ActionText.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -15,8 +15,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . -#include - #include "ActionText.h" #include "MsiHelper.h" #include "JsonHelper.h" diff --git a/installer/ActionTextTable.cpp b/installer/ActionTextTable.cpp index 49585a30b14..1a6ed4a04ad 100644 --- a/installer/ActionTextTable.cpp +++ b/installer/ActionTextTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,11 +20,56 @@ #include "ActionTextTable.h" ActionTextTable::ActionTextTable(const nlohmann::json& json, - InstallerStrings& installerStrings) { + InstallerStrings& installerStrings, + std::shared_ptr validationTable) { std::cout << "Loading ActionTextTable..." << std::endl; for (const auto& item : json) { values.emplace_back(item, installerStrings); } + const auto tableName = std::string("ActionText"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/actiontext-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Action", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of the action.", url) + )); + validationTable->add(Validation( + tableName, + "Description", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("Localized description that is displayed in " + "the progress dialog box, or written to the log when the " + "action is executing.", url) + )); + validationTable->add(Validation( + tableName, + "Template", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryTemplate, + "", + DescriptionWithUrl("A localized format template that is used to " + "format action data records to display during action " + "execution.", url) + )); + } } bool ActionTextTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/ActionTextTable.h b/installer/ActionTextTable.h index 10e0ee89e1d..60a21c2cd5d 100644 --- a/installer/ActionTextTable.h +++ b/installer/ActionTextTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -22,11 +22,13 @@ #include "ActionText.h" #include "InstallerStrings.h" #include "Generator.h" +#include "ValidationTable.h" class ActionTextTable : public Generator{ public: explicit ActionTextTable(const nlohmann::json& json, - InstallerStrings& installerStrings); + InstallerStrings& installerStrings, + std::shared_ptr validationTable); ~ActionTextTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/AdminExecuteSequenceTable.cpp b/installer/AdminExecuteSequenceTable.cpp index 0d0c9d910f4..d9a0a281242 100644 --- a/installer/AdminExecuteSequenceTable.cpp +++ b/installer/AdminExecuteSequenceTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -18,12 +18,55 @@ #include "AdminExecuteSequenceTable.h" AdminExecuteSequenceTable::AdminExecuteSequenceTable( - const nlohmann::json& json) { + const nlohmann::json& json, + std::shared_ptr validationTable) { std::cout << "Loading AdminExecuteSequenceTable..." << std::endl; for (const auto& value : json) { actions.emplace_back(value); } + + const auto tableName = std::string("AdminExecuteSequence"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/adminexecutesequence-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Action", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of the action to execute.", url) + )); + validationTable->add(Validation( + tableName, + "Condition", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCondition, + "", + DescriptionWithUrl("Logical expression.", url) + )); + validationTable->add(Validation( + tableName, + "Sequence", + true, + -4, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("A positive value indicates the " + "sequence position of the action.", url) + )); + } } bool AdminExecuteSequenceTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/AdminExecuteSequenceTable.h b/installer/AdminExecuteSequenceTable.h index f9c064c3e8a..38f76594bea 100644 --- a/installer/AdminExecuteSequenceTable.h +++ b/installer/AdminExecuteSequenceTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -22,10 +22,12 @@ #include "Action.h" #include "Generator.h" +#include "ValidationTable.h" class AdminExecuteSequenceTable : public Generator{ public: - explicit AdminExecuteSequenceTable(const nlohmann::json& json); + explicit AdminExecuteSequenceTable(const nlohmann::json& json, + std::shared_ptr validationTable); ~AdminExecuteSequenceTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/AdminUISequenceTable.cpp b/installer/AdminUISequenceTable.cpp index 0037708c878..1e4df8e8689 100644 --- a/installer/AdminUISequenceTable.cpp +++ b/installer/AdminUISequenceTable.cpp @@ -17,11 +17,56 @@ #include "AdminUISequenceTable.h" -AdminUISequenceTable::AdminUISequenceTable(const nlohmann::json& json) { +AdminUISequenceTable::AdminUISequenceTable(const nlohmann::json& json, + std::shared_ptr validationTable) { std::cout << "Loading AdminUISequenceTable..." << std::endl; for (const auto& value : json) { actions.emplace_back(value); } + + const auto tableName = std::string("AdminUISequence"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/adminuisequence-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Action", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of the action to execute.", url) + )); + validationTable->add(Validation( + tableName, + "Condition", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCondition, + "", + DescriptionWithUrl("Logical expression.", url) + )); + validationTable->add(Validation( + tableName, + "Sequence", + true, + -4, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("A positive value indicates the sequence " + "position of the action. The negative values indicate that " + "the action is called if the installer returns the " + "termination flag.", url) + )); + } } bool AdminUISequenceTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/AdminUISequencetable.h b/installer/AdminUISequencetable.h index 3c99c28c307..a8ed418c1b7 100644 --- a/installer/AdminUISequencetable.h +++ b/installer/AdminUISequencetable.h @@ -19,10 +19,12 @@ #include "Action.h" #include "Generator.h" +#include "ValidationTable.h" class AdminUISequenceTable : public Generator { public: - explicit AdminUISequenceTable(const nlohmann::json& json); + explicit AdminUISequenceTable(const nlohmann::json& json, + std::shared_ptr validationTable); ~AdminUISequenceTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/AdvtExecuteSequenceTable.cpp b/installer/AdvtExecuteSequenceTable.cpp index 113915a922d..fae22373fac 100644 --- a/installer/AdvtExecuteSequenceTable.cpp +++ b/installer/AdvtExecuteSequenceTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -18,12 +18,58 @@ #include "AdvtExecuteSequenceTable.h" AdvtExecuteSequenceTable::AdvtExecuteSequenceTable( - const nlohmann::json& json) { + const nlohmann::json& json, + std::shared_ptr validationTable) { std::cout << "Loading AdvtExecuteSequenceTable..." << std::endl; for (const auto& value : json) { actions.emplace_back(value); } + + const auto tableName = std::string("AdvtExecuteSequence"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/advtexecutesequence-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Action", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of the standard action the installer is " + "to execute.", url) + )); + validationTable->add(Validation( + tableName, + "Condition", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCondition, + "", + DescriptionWithUrl("Logical expression.", url) + )); + validationTable->add(Validation( + tableName, + "Sequence", + true, + -4, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("A positive value indicates the sequence " + "position of the action. The negative values indicate that " + "the action is called if the installer returns the " + "termination flag.", url) + )); + } } bool AdvtExecuteSequenceTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/AdvtExecuteSequenceTable.h b/installer/AdvtExecuteSequenceTable.h index 8b711bb1d13..4f7a6593861 100644 --- a/installer/AdvtExecuteSequenceTable.h +++ b/installer/AdvtExecuteSequenceTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,10 +19,12 @@ #include "Action.h" #include "Generator.h" +#include "ValidationTable.h" class AdvtExecuteSequenceTable : public Generator { public: - explicit AdvtExecuteSequenceTable(const nlohmann::json& json); + explicit AdvtExecuteSequenceTable(const nlohmann::json& json, + std::shared_ptr validationTable); ~AdvtExecuteSequenceTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/BinaryTable.cpp b/installer/BinaryTable.cpp index cac5ee517cf..6bda524ac7d 100644 --- a/installer/BinaryTable.cpp +++ b/installer/BinaryTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,19 +19,50 @@ BinaryTable::BinaryTable(const nlohmann::json& json, const std::filesystem::path& path, const std::string& platform, - const std::string& configuration) { + const std::string& configuration, + std::shared_ptr validationTable) { std::cout << "Loading BinaryTable..." << std::endl; for (const auto& element : json) { binaries.emplace_back(element, path, platform, configuration); } + + const auto tableName = std::string("Binary"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/binary-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Name", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("A unique key that identifies the particular " + "binary data.", url) + )); + validationTable->add(Validation( + tableName, + "Data", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryBinary, + "", + DescriptionWithUrl("The unformatted binary data.", url) + )); + } } bool BinaryTable::generate(MSIHANDLE hDatabase) { std::cout << "Generating BinaryTable..." << std::endl; const auto sql_create = "CREATE TABLE `Binary` (`Name` CHAR(72) NOT NULL, " - "`Data` OBJECT PRIMARY KEY `Name`)"; + "`Data` OBJECT NOT NULL PRIMARY KEY `Name`)"; const auto sql_insert = "INSERT INTO `Binary` (`Name`, `Data`) " "VALUES (?, ?)"; diff --git a/installer/BinaryTable.h b/installer/BinaryTable.h index 3b4611bc0d9..145c3e0180d 100644 --- a/installer/BinaryTable.h +++ b/installer/BinaryTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -21,12 +21,14 @@ #include "Generator.h" #include "Binary.h" +#include "ValidationTable.h" class BinaryTable : public Generator { public: explicit BinaryTable(const nlohmann::json& json, const std::filesystem::path& path, const std::string& platform, - const std::string& configuration); + const std::string& configuration, + std::shared_ptr validationTable); ~BinaryTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/CheckboxTable.cpp b/installer/CheckboxTable.cpp index 24ccd3142d6..7e9a0b02654 100644 --- a/installer/CheckboxTable.cpp +++ b/installer/CheckboxTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,20 +17,52 @@ #include "CheckboxTable.h" -CheckboxTable::CheckboxTable(const nlohmann::json& json) { +CheckboxTable::CheckboxTable(const nlohmann::json& json, + std::shared_ptr validationTable) { std::cout << "Loading CheckboxTable..." << std::endl; for (const auto& item : json) { properties.emplace_back(item); } + + const auto tableName = std::string("CheckBox"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/checkbox-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Property", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("A named property to be tied to this item.", + url) + )); + validationTable->add(Validation( + tableName, + "Value", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("The value string associated with this item.", + url) + )); + } } bool CheckboxTable::generate(MSIHANDLE hDatabase) { std::cout << "Generating CheckboxTable..." << std::endl; - const auto sql_create = "CREATE TABLE `Checkbox` " + const auto sql_create = "CREATE TABLE `CheckBox` " "(`Property` CHAR(72) NOT NULL, `Value` CHAR(64) " "PRIMARY KEY `Property`)"; - const auto sql_insert = "INSERT INTO `Checkbox` " + const auto sql_insert = "INSERT INTO `CheckBox` " "(`Property`, `Value`) VALUES (?, ?)"; return Generator::generate(hDatabase, sql_create, sql_insert, properties); diff --git a/installer/CheckboxTable.h b/installer/CheckboxTable.h index b08ef2bdaf0..ef09e81d4e3 100644 --- a/installer/CheckboxTable.h +++ b/installer/CheckboxTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,10 +19,12 @@ #include "Generator.h" #include "Checkbox.h" +#include "ValidationTable.h" class CheckboxTable : public Generator { public: - explicit CheckboxTable(const nlohmann::json& json); + explicit CheckboxTable(const nlohmann::json& json, + std::shared_ptr validationTable); ~CheckboxTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/Component.cpp b/installer/Component.cpp index 2649db12017..aa77c49a09c 100644 --- a/installer/Component.cpp +++ b/installer/Component.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -33,7 +33,7 @@ Component::Component(const nlohmann::json& json, const std::string& directory, component = parent + "_" + directory; } JsonHelper::handle(json, "Files", [&](const auto& file) { - files.emplace_back(file, component); + files.emplace_back(file, component, directory); }); JsonHelper::handle(json, "Registry", [&](const auto& registry) { registries.emplace_back(registry, component); @@ -99,3 +99,11 @@ std::vector Component::getServiceInstalls() const { std::vector Component::getShortcuts() const { return shortcuts; } + +std::string Component::getComponentName() const { + return component; +} + +std::string Component::getDirectory() const { + return directory; +} diff --git a/installer/Component.h b/installer/Component.h index a6da3cb8780..0c4c4a8dc05 100644 --- a/installer/Component.h +++ b/installer/Component.h @@ -45,6 +45,8 @@ class Component : public Record { std::vector getServiceControls() const; std::vector getServiceInstalls() const; std::vector getShortcuts() const; + std::string getComponentName() const; + std::string getDirectory() const; private: std::string component{}; std::string componentId{}; diff --git a/installer/ComponentTable.cpp b/installer/ComponentTable.cpp index f486ab4ade8..cab93e0d933 100644 --- a/installer/ComponentTable.cpp +++ b/installer/ComponentTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,12 +17,96 @@ #include "ComponentTable.h" -ComponentTable::ComponentTable(const std::vector& directories) { +ComponentTable::ComponentTable(const std::vector& directories, + std::shared_ptr validationTable) { for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { components.push_back(component); } } + + const auto tableName = std::string("Component"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/component-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Component", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Identifies the component record.", url) + )); + validationTable->add(Validation( + tableName, + "ComponentId", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryGuid, + "", + DescriptionWithUrl("A string GUID unique to this component, " + "version, and language.", url) + )); + validationTable->add(Validation( + tableName, + "Directory_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Directory", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("External key of an entry in the Directory " + "table.", url) + )); + validationTable->add(Validation( + tableName, + "Attributes", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("This column contains a bit flag that " + "specifies options for remote execution.", url) + )); + validationTable->add(Validation( + tableName, + "Condition", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCondition, + "", + DescriptionWithUrl("This column contains a conditional statement " + "that can control whether a component is installed.", url) + )); + validationTable->add(Validation( + tableName, + "KeyPath", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "File;Registry", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("This value points to a file or folder " + "belonging to the component that the installer uses to detect " + "the component.", url) + )); + } } bool ComponentTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/ComponentTable.h b/installer/ComponentTable.h index 23d0047bc5f..76c96614d2a 100644 --- a/installer/ComponentTable.h +++ b/installer/ComponentTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "Generator.h" #include "Component.h" #include "Directory.h" +#include "ValidationTable.h" class ComponentTable : public Generator { public: - explicit ComponentTable(const std::vector& directories); + explicit ComponentTable(const std::vector& directories, + std::shared_ptr validationTable); ~ComponentTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/ControlConditionTable.cpp b/installer/ControlConditionTable.cpp index a669a610984..159095c6d96 100644 --- a/installer/ControlConditionTable.cpp +++ b/installer/ControlConditionTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,7 +19,65 @@ #include "ControlConditionTable.h" ControlConditionTable::ControlConditionTable( - const std::vector& controls) noexcept : controls(controls) {} + const std::vector& controls, + std::shared_ptr validationTable) : controls(controls) { + const auto tableName = std::string("ControlCondition"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/controlcondition-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Dialog_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Dialog", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key to the first column of the " + "Dialog table.", url) + )); + validationTable->add(Validation( + tableName, + "Control_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Control", + 2, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key to the second column of the " + "Control table.", url) + )); + validationTable->add(Validation( + tableName, + "Action", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "Default;Disable;Enable;Hide;Show", + DescriptionWithUrl("The action that is to be taken on the " + "control.", url) + )); + validationTable->add(Validation( + tableName, + "Condition", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCondition, + "", + DescriptionWithUrl("A conditional statement that specifies under " + "which conditions the action should be triggered.", url) + )); + } +} bool ControlConditionTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/ControlConditionTable.h b/installer/ControlConditionTable.h index 8351dd281c1..698a8d8e488 100644 --- a/installer/ControlConditionTable.h +++ b/installer/ControlConditionTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -22,11 +22,13 @@ #include "Generator.h" #include "ControlCondition.h" #include "Control.h" +#include "ValidationTable.h" class ControlConditionTable : public Generator { public: explicit ControlConditionTable( - const std::vector& controls) noexcept; + const std::vector& controls, + std::shared_ptr validationTable); ~ControlConditionTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/ControlEventTable.cpp b/installer/ControlEventTable.cpp index b2cfa55d5bd..139bd221aeb 100644 --- a/installer/ControlEventTable.cpp +++ b/installer/ControlEventTable.cpp @@ -18,7 +18,93 @@ #include "ControlEventTable.h" ControlEventTable::ControlEventTable( - const std::vector& controls) noexcept : controls(controls) {} + const std::vector& controls, + std::shared_ptr validationTable) : controls(controls) { + const auto tableName = std::string("ControlEvent"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/controlevent-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Dialog_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Dialog", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key to the first column of the " + "Dialog table.", url) + )); + validationTable->add(Validation( + tableName, + "Control_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Control", + 2, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key to the second column of the " + "Control table.", url) + )); + validationTable->add(Validation( + tableName, + "Event", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("An identifier that specifies the type of " + "event that should take place when the user interacts with " + "the control specified by Dialog_ and Control_.", url) + )); + validationTable->add(Validation( + tableName, + "Argument", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("A value used as a modifier when triggering a " + "particular event.", url) + )); + validationTable->add(Validation( + tableName, + "Condition", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCondition, + "", + DescriptionWithUrl("A conditional statement that determines " + "whether the installer activates the event in the Event " + "column.", url) + )); + validationTable->add(Validation( + tableName, + "Ordering", + true, + 0, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("An integer used to order several events tied " + "to the same control.", url) + )); + } +} bool ControlEventTable::generate(MSIHANDLE hDatabase) { std::cout << "Generating ControlEventTable..." << std::endl; diff --git a/installer/ControlEventTable.h b/installer/ControlEventTable.h index 6e947f3185f..92f53b61aa4 100644 --- a/installer/ControlEventTable.h +++ b/installer/ControlEventTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "ControlEvent.h" #include "Generator.h" #include "Control.h" +#include "ValidationTable.h" class ControlEventTable : public Generator { public: - explicit ControlEventTable(const std::vector& controls) noexcept; + explicit ControlEventTable(const std::vector& controls, + std::shared_ptr validationTable); ~ControlEventTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/ControlTable.cpp b/installer/ControlTable.cpp index 57b7fd43004..e2ff33bddb3 100644 --- a/installer/ControlTable.cpp +++ b/installer/ControlTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -23,8 +23,170 @@ #include "RadioButtonTable.h" #include "ControlTable.h" -ControlTable::ControlTable(const std::vector& dialogs) noexcept : - dialogs(dialogs) {} +ControlTable::ControlTable(const std::vector& dialogs, + std::shared_ptr validationTable) : dialogs(dialogs), + validationTable(validationTable) { + const auto tableName = std::string("Control"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/control-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Dialog_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Dialog", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("External key to the first column of the " + "Dialog table, the name of the dialog box.", url) + )); + validationTable->add(Validation( + tableName, + "Control", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of the control.", url) + )); + validationTable->add(Validation( + tableName, + "Type", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The type of the control.", url) + )); + validationTable->add(Validation( + tableName, + "X", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Horizontal coordinate of the upper-left " + "corner of the rectangular boundary of the control.", url) + )); + validationTable->add(Validation( + tableName, + "Y", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Vertical coordinate of the upper-left corner " + "of the rectangular boundary of the control.", url) + )); + validationTable->add(Validation( + tableName, + "Width", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Width of the rectangular boundary of the " + "control.", url) + )); + validationTable->add(Validation( + tableName, + "Height", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Height of the rectangular boundary of the " + "control.", url) + )); + validationTable->add(Validation( + tableName, + "Attributes", + true, + 0, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("A 32-bit word that specifies the bit flags to " + "be applied to this control.", url) + )); + validationTable->add(Validation( + tableName, + "Property", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The name of a defined property to be linked " + "to this control.", + url) + )); + validationTable->add(Validation( + tableName, + "Text", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("A localizable string used to set the initial " + "text contained in a control.", url) + )); + validationTable->add(Validation( + tableName, + "Control_Next", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Control", + 2, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The name of another control on the same " + "dialog box and an external key to the second column of the " + "Control table.", url) + )); + validationTable->add(Validation( + tableName, + "Help", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("Optional, localizable text strings that are " + "used with the Help button.", url) + )); + } +} bool ControlTable::generate(MSIHANDLE hDatabase) { @@ -35,19 +197,19 @@ bool ControlTable::generate(MSIHANDLE hDatabase) } } - if (!ControlConditionTable(controls).generate(hDatabase)) { + if (!ControlConditionTable(controls, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate ControlConditionTable" << std::endl; return false; } - if (!ControlEventTable(controls).generate(hDatabase)) { + if (!ControlEventTable(controls, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate ControlEventTable" << std::endl; return false; } - if (!EventMappingTable(controls).generate(hDatabase)) { + if (!EventMappingTable(controls, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate EventMappingTable" << std::endl; return false; } - if (!RadioButtonTable(controls).generate(hDatabase)) { + if (!RadioButtonTable(controls, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate RadioButtonTable" << std::endl; return false; } diff --git a/installer/ControlTable.h b/installer/ControlTable.h index f5c5bb1ccb1..07610182ca0 100644 --- a/installer/ControlTable.h +++ b/installer/ControlTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,13 +20,15 @@ #include #include "Dialog.h" #include "Control.h" - +#include "ValidationTable.h" class ControlTable : Generator { public: - explicit ControlTable(const std::vector& dialogs) noexcept; + explicit ControlTable(const std::vector& dialogs, + std::shared_ptr validationTable); ~ControlTable() = default; bool generate(MSIHANDLE hDatabase) override; private: const std::vector& dialogs; + std::shared_ptr validationTable; }; diff --git a/installer/CreateFolderTable.cpp b/installer/CreateFolderTable.cpp index 453a652d95b..8bea2db6518 100644 --- a/installer/CreateFolderTable.cpp +++ b/installer/CreateFolderTable.cpp @@ -18,7 +18,8 @@ #include "CreateFolderTable.h" CreateFolderTable::CreateFolderTable( - const std::vector& directories) { + const std::vector& directories, + std::shared_ptr validationTable) { for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { auto [result, record] = component.getCreateFolder(); @@ -27,6 +28,37 @@ CreateFolderTable::CreateFolderTable( } } } + + const auto tableName = std::string("CreateFolder"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/createfolder-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Directory_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Directory", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("External key into the first column of the " + "Directory table.", url) + )); + validationTable->add(Validation( + tableName, + "Component_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Component", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("External key into the first column of the " + "Component table.", url) + )); + } } bool CreateFolderTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/CreateFolderTable.h b/installer/CreateFolderTable.h index 3bc79a29922..a46c9de5b54 100644 --- a/installer/CreateFolderTable.h +++ b/installer/CreateFolderTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "Generator.h" #include "Directory.h" #include "CreateFolder.h" +#include "ValidationTable.h" class CreateFolderTable : public Generator { public: - CreateFolderTable(const std::vector& directories); + CreateFolderTable(const std::vector& directories, + std::shared_ptr validationTable); ~CreateFolderTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/CustomActionTable.cpp b/installer/CustomActionTable.cpp index e411a145db1..1afb44c2b5b 100644 --- a/installer/CustomActionTable.cpp +++ b/installer/CustomActionTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,11 +17,80 @@ #include "CustomActionTable.h" -CustomActionTable::CustomActionTable(const nlohmann::json& json) { +CustomActionTable::CustomActionTable(const nlohmann::json& json, + std::shared_ptr validationTable) { std::cout << "Loading CustomActionTable..." << std::endl; for (const auto& customAction : json) { customActions.emplace_back(customAction); } + + const auto tableName = std::string("CustomAction"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/customaction-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Action", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of the action.", url) + )); + validationTable->add(Validation( + tableName, + "Type", + false, + 1, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("A field of flag bits specifying the basic " + "type of custom action and options.", url) + )); + validationTable->add(Validation( + tableName, + "Source", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCustomSource, + "", + DescriptionWithUrl("A property name or external key into another " + "table.", url) + )); + validationTable->add(Validation( + tableName, + "Target", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("An execution parameter that depends on the " + "basic type of custom action.", url) + )); + validationTable->add(Validation( + tableName, + "ExtendedType", + true, + 0, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The extended type of the action.", url) + )); + } } bool CustomActionTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/CustomActionTable.h b/installer/CustomActionTable.h index 22a484f96ab..ed721aad732 100644 --- a/installer/CustomActionTable.h +++ b/installer/CustomActionTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,10 +19,12 @@ #include "Generator.h" #include "CustomAction.h" +#include "ValidationTable.h" class CustomActionTable : public Generator { public: - explicit CustomActionTable(const nlohmann::json& json); + explicit CustomActionTable(const nlohmann::json& json, + std::shared_ptr validationTable); ~CustomActionTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/DialogTable.cpp b/installer/DialogTable.cpp index 1129e61a078..b66ea03d5f7 100644 --- a/installer/DialogTable.cpp +++ b/installer/DialogTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,16 +20,153 @@ #include "Generator.h" DialogTable::DialogTable(const nlohmann::json& json, - InstallerStrings& installerStrings) { + InstallerStrings& installerStrings, + std::shared_ptr validationTable) : + validationTable(validationTable) { std::cout << "Loading DialogTable..." << std::endl; for (const auto& dialog : json) { dialogs.emplace_back(dialog, installerStrings); } + + const auto tableName = std::string("Dialog"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/dialog-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Dialog", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The primary key and name of the dialog box.", + url) + )); + validationTable->add(Validation( + tableName, + "HCentering", + false, + 0, + 100, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The horizontal position of the dialog box.", + url) + )); + validationTable->add(Validation( + tableName, + "VCentering", + false, + 0, + 100, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The vertical position of the dialog box.", url) + )); + validationTable->add(Validation( + tableName, + "Width", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The width of the rectangular boundary of the " + "dialog box.", url) + )); + validationTable->add(Validation( + tableName, + "Height", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The height of the rectangular boundary of the " + "dialog box.", url) + )); + validationTable->add(Validation( + tableName, + "Attributes", + true, + 0, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("A 32-bit word that specifies the attribute " + "flags to be applied to this dialog box.", url) + )); + validationTable->add(Validation( + tableName, + "Title", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("A localizable text string specifying the " + "title to be displayed in the title bar of the dialog box.", + url) + )); + validationTable->add(Validation( + tableName, + "Control_First", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Control", + 2, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key to the second column of the " + "Control Table.", url) + )); + validationTable->add(Validation( + tableName, + "Control_Default", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Control", + 2, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key to the second column of the " + "Control Table.", url) + )); + validationTable->add(Validation( + tableName, + "Control_Cancel", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Control", + 2, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key to the second column of the " + "Control Table.", url) + )); + } } bool DialogTable::generate(MSIHANDLE hDatabase) { - if (!ControlTable(dialogs).generate(hDatabase)) { + if (!ControlTable(dialogs, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate ControlTable" << std::endl; return false; } diff --git a/installer/DialogTable.h b/installer/DialogTable.h index 5ac8a0de029..a1135303466 100644 --- a/installer/DialogTable.h +++ b/installer/DialogTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,13 +20,16 @@ #include "Dialog.h" #include "InstallerStrings.h" #include "Generator.h" +#include "ValidationTable.h" class DialogTable : public Generator { public: explicit DialogTable(const nlohmann::json& json, - InstallerStrings& installerStrings); + InstallerStrings& installerStrings, + std::shared_ptr validationTable); ~DialogTable() = default; bool generate(MSIHANDLE hDatabase) override; private: std::vector dialogs{}; + std::shared_ptr validationTable; }; diff --git a/installer/Directory.cpp b/installer/Directory.cpp index f87034ef4b8..b3e815c5dd0 100644 --- a/installer/Directory.cpp +++ b/installer/Directory.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License diff --git a/installer/DirectoryTable.cpp b/installer/DirectoryTable.cpp index 812facae9ca..0ea5335cb2d 100644 --- a/installer/DirectoryTable.cpp +++ b/installer/DirectoryTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -31,54 +31,102 @@ DirectoryTable::DirectoryTable(const nlohmann::json& json, const std::filesystem::path& root_path, const std::filesystem::path& output_path, InstallerStrings& installerStrings, const std::string& platform, - const std::string& configuration) : + const std::string& configuration, + std::shared_ptr validationTable) : root_path(root_path), output_path(output_path), platform(platform), - configuration(configuration) { + configuration(configuration), validationTable(validationTable) { std::cout << "Loading DirectoryTable..." << std::endl; for (const auto& directory : json) { directories.emplace_back(directory, "", installerStrings); } + + const auto tableName = std::string("Directory"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/directory-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Directory", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The Directory column contains a unique " + "identifier for a directory or directory path.", url) + )); + validationTable->add(Validation( + tableName, + "Directory_Parent", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Directory", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("This column is a reference to the directory's " + "parent directory.", url) + )); + validationTable->add(Validation( + tableName, + "DefaultDir", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryDefaultDir, + "", + DescriptionWithUrl("The DefaultDir column contains the " + "directory's name (localizable)under the parent directory.", + url) + )); + } } bool DirectoryTable::generate(MSIHANDLE hDatabase) { - if (!ComponentTable(directories).generate(hDatabase)) { + if (!ComponentTable(directories, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate ComponentTable" << std::endl; return false; } - if (!FeatureComponentsTable(directories).generate(hDatabase)) { + if (!FeatureComponentsTable(directories,validationTable).generate( + hDatabase)) { std::cerr << "Failed to generate FeatureComponentsTable" << std::endl; return false; } - if (!CreateFolderTable(directories).generate(hDatabase)) { + if (!CreateFolderTable(directories, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate CreateFolderTable" << std::endl; return false; } if (!FileTable(directories, root_path, - output_path, platform, configuration).generate(hDatabase)) { + output_path, platform, configuration, validationTable).generate( + hDatabase)) { std::cerr << "Failed to generate FileTable" << std::endl; return false; } - if (!FontTable(directories).generate(hDatabase)) { + if (!FontTable(directories, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate FontTable" << std::endl; return false; } - if (!RegistryTable(directories).generate(hDatabase)) { + if (!RegistryTable(directories, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate RegistryTable" << std::endl; return false; } - if (!RemoveFileTable(directories).generate(hDatabase)) { + if (!RemoveFileTable(directories, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate RemoveFileTable" << std::endl; return false; } - if (!ServiceControlTable(directories).generate(hDatabase)) { + if (!ServiceControlTable(directories, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate ServiceControlTable" << std::endl; return false; } - if (!ServiceInstallTable(directories).generate(hDatabase)) { + if (!ServiceInstallTable(directories, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate ServiceInstallTable" << std::endl; return false; } - if (!ShortcutTable(directories).generate(hDatabase)) { + if (!ShortcutTable(directories, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate ShortcutTable" << std::endl; return false; } diff --git a/installer/DirectoryTable.h b/installer/DirectoryTable.h index 3ed1825e2d3..78e42fb6af2 100644 --- a/installer/DirectoryTable.h +++ b/installer/DirectoryTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,6 +19,7 @@ #include "Generator.h" #include "Directory.h" +#include "ValidationTable.h" class DirectoryTable : public Generator { public: @@ -26,7 +27,8 @@ class DirectoryTable : public Generator { const std::filesystem::path& root_path, const std::filesystem::path& output_path, InstallerStrings& installerStrings, const std::string& platform, - const std::string& configuration); + const std::string& configuration, + std::shared_ptr validationTable); ~DirectoryTable() = default; bool generate(MSIHANDLE hDatabase) override; private: @@ -35,4 +37,5 @@ class DirectoryTable : public Generator { std::filesystem::path output_path{}; std::string platform{}; std::string configuration{}; + std::shared_ptr validationTable; }; diff --git a/installer/ErrorTable.cpp b/installer/ErrorTable.cpp index dc1d8b345d6..475e8eacfba 100644 --- a/installer/ErrorTable.cpp +++ b/installer/ErrorTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -18,12 +18,43 @@ #include "ErrorTable.h" ErrorTable::ErrorTable(const nlohmann::json& json, - InstallerStrings& installerStrings) { + InstallerStrings& installerStrings, + std::shared_ptr validationTable) { std::cout << "Loading ErrorTable..." << std::endl; for (const auto& error : json) { errors.emplace_back(error, installerStrings); } + + const auto tableName = std::string("Error"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/error-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Error", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The error number.", url) + )); + validationTable->add(Validation( + tableName, + "Message", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryTemplate, + "", + DescriptionWithUrl("This column contains the localizable error " + "formatting template. ", url) + )); + } } bool ErrorTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/ErrorTable.h b/installer/ErrorTable.h index f75bce22462..030e57e0993 100644 --- a/installer/ErrorTable.h +++ b/installer/ErrorTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,11 +20,13 @@ #include "Generator.h" #include "InstallerStrings.h" #include "Error.h" +#include "ValidationTable.h" class ErrorTable : public Generator { public: explicit ErrorTable(const nlohmann::json& json, - InstallerStrings& installerStrings); + InstallerStrings& installerStrings, + std::shared_ptr validationTable); ~ErrorTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/EventMappingTable.cpp b/installer/EventMappingTable.cpp index 6434b113283..9b9e4623ed0 100644 --- a/installer/EventMappingTable.cpp +++ b/installer/EventMappingTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,8 +17,65 @@ #include "EventMappingTable.h" -EventMappingTable::EventMappingTable(const std::vector& controls) : - controls(controls) { +EventMappingTable::EventMappingTable(const std::vector& controls, + std::shared_ptr validationTable) : controls(controls) { + const auto tableName = std::string("EventMapping"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/eventmapping-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Dialog_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Dialog", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key to the first column of the " + "Dialog Table.", url) + )); + validationTable->add(Validation( + tableName, + "Control_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Control", + 2, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key to the second column of the " + "Control Table.", url) + )); + validationTable->add(Validation( + tableName, + "Event", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("This field is an identifier that specifies " + "the type of event that is subscribed to by the control.", url) + )); + validationTable->add(Validation( + tableName, + "Attribute", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The Argument of the event is passed as the " + "argument of the attribute call to change this attribute of " + "the control.", url) + )); + } } bool EventMappingTable::generate(MSIHANDLE hDatabase) { @@ -33,7 +90,7 @@ bool EventMappingTable::generate(MSIHANDLE hDatabase) { const auto sql_create = "CREATE TABLE `EventMapping` " "(`Dialog_` CHAR(72) NOT NULL, `Control_` CHAR(50) NOT NULL, " - "`Event` CHAR(50) NOT NULL, `Attribute` CHAR(50) " + "`Event` CHAR(50) NOT NULL, `Attribute` CHAR(50) NOT NULL " "PRIMARY KEY Dialog_, Control_, Event)"; const auto sql_insert = "INSERT INTO `EventMapping` " "(`Dialog_`, `Control_`, `Event`, `Attribute`) VALUES (?, ?, ?, ?)"; diff --git a/installer/EventMappingTable.h b/installer/EventMappingTable.h index 1ae693e3fb6..a90f1b4bc2a 100644 --- a/installer/EventMappingTable.h +++ b/installer/EventMappingTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "Generator.h" #include "EventMapping.h" #include "Control.h" +#include "ValidationTable.h" class EventMappingTable : public Generator { public: - explicit EventMappingTable(const std::vector& controls); + explicit EventMappingTable(const std::vector& controls, + std::shared_ptr validationTable); ~EventMappingTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/FeatureComponentsTable.cpp b/installer/FeatureComponentsTable.cpp index 3baae93be2d..9ceddd672ed 100644 --- a/installer/FeatureComponentsTable.cpp +++ b/installer/FeatureComponentsTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -18,12 +18,42 @@ #include "FeatureComponentsTable.h" FeatureComponentsTable::FeatureComponentsTable( - const std::vector& directories) { + const std::vector& directories, + std::shared_ptr validationTable) { for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { featureComponents.emplace_back(component.getFeatureComponent()); } } + + const auto tableName = std::string("FeatureComponents"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/featurecomponents-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Feature_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Feature", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key into the first column of the Feature table.", url) + )); + validationTable->add(Validation( + tableName, + "Component_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Component", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An external key into the first column of the Component table.", url) + )); + } } bool FeatureComponentsTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/FeatureComponentsTable.h b/installer/FeatureComponentsTable.h index c16d4aee100..2c51a73288b 100644 --- a/installer/FeatureComponentsTable.h +++ b/installer/FeatureComponentsTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "Generator.h" #include "FeatureComponents.h" #include "Directory.h" +#include "ValidationTable.h" class FeatureComponentsTable : public Generator { public: - explicit FeatureComponentsTable(const std::vector& directories); + explicit FeatureComponentsTable(const std::vector& directories, + std::shared_ptr validationTable); ~FeatureComponentsTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/FeatureTable.cpp b/installer/FeatureTable.cpp index b4ff505fcfa..a44d9e45078 100644 --- a/installer/FeatureTable.cpp +++ b/installer/FeatureTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -18,11 +18,124 @@ #include "FeatureTable.h" FeatureTable::FeatureTable(const nlohmann::json& json, - InstallerStrings& installerStrings) { + InstallerStrings& installerStrings, + std::shared_ptr validationTable) { std::cout << "Loading FeatureTable..." << std::endl; for (const auto& feature : json) { features.emplace_back(feature, "", installerStrings); } + + const auto tableName = std::string("Feature"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/feature-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Feature", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The primary key that is used to identify a " + "specific feature record.", url) + )); + validationTable->add(Validation( + tableName, + "Feature_Parent", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Feature", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("An optional key of a parent record in the " + "same table.", url) + )); + validationTable->add(Validation( + tableName, + "Title", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("A short string of text that identifies a " + "feature.", url) + )); + validationTable->add(Validation( + tableName, + "Description", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("A longer string of text that describes a " + "feature.", url) + )); + validationTable->add(Validation( + tableName, + "Display", + true, + 0, + 3276, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The number in this field specifies the order " + "in which the feature is to be displayed in the user " + "interface.", url) + )); + validationTable->add(Validation( + tableName, + "Level", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The initial installation level of this " + "feature.", url) + )); + validationTable->add(Validation( + tableName, + "Directory_", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Directory", + 1, + ValidationCategoryUpperCase, + "", + DescriptionWithUrl("The Directory_ column specifies the name of a " + "directory that can be configured by a Selection Dialog.", url) + )); + validationTable->add(Validation( + tableName, + "Attributes", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "0;1;2;4;5;6;8;9;10;16;17;18;20;21;22;24;25;26;32;33;34;36;37;38;" + "48;49;50;52;53;54", + DescriptionWithUrl("The remote execution option for features that " + "are not installed and for which no feature state request is " + "made by using any of the following properties.", url) + )); + } } bool FeatureTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/FeatureTable.h b/installer/FeatureTable.h index 95056f136ee..8dfece6fe60 100644 --- a/installer/FeatureTable.h +++ b/installer/FeatureTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,11 +19,13 @@ #include "Generator.h" #include "Feature.h" +#include "ValidationTable.h" class FeatureTable : public Generator { public: explicit FeatureTable(const nlohmann::json& json, - InstallerStrings& installerStrings); + InstallerStrings& installerStrings, + std::shared_ptr validationTable); ~FeatureTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/File.cpp b/installer/File.cpp index 2df40f50537..d632f5b32c4 100644 --- a/installer/File.cpp +++ b/installer/File.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -15,21 +15,23 @@ // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . +#include + #include "File.h" #include "MsiHelper.h" #include "JsonHelper.h" -File::File(const nlohmann::json& json, const std::string& component) : - component(component) { - JsonHelper::get(json, "File", file); - JsonHelper::get(json, "FileName", filename); +File::File(const nlohmann::json& json, const std::string& component, + const std::string& directory) : + component(component), directory(directory) { JsonHelper::get(json, "FilePath", filepath); JsonHelper::get(json, "IsFont", isFont); } MSIHANDLE File::getRecord() const { - return MsiHelper::MsiRecordSet({ file, component, filename, filesize, - version, language, attributes, sequence }); + return MsiHelper::MsiRecordSet({ getFileId(), component, + filename_short + "|" + filename_long, filesize, version, language, + attributes, sequence }); } std::filesystem::path File::getFilepath() const { @@ -37,7 +39,14 @@ std::filesystem::path File::getFilepath() const { } std::string File::getFileId() const { - return file; + auto result = component + "_" + filepath.filename().string(); + std::transform(result.begin(), result.end(), result.begin(), [](auto c) { + if (c == '-') { + return '_'; + } + return c; + }); + return result; } bool File::isFontFile() const noexcept { @@ -67,3 +76,27 @@ void File::setSequence(int s) noexcept { void File::setFilepath(const std::filesystem::path& p) { filepath = p; } + +void File::setShortFileName(const std::string& n) { + filename_short = n; +} + +void File::setLongFileName(const std::string& n) { + filename_long = n; +} + +std::string File::getShortFileName() const { + return filename_short; +} + +std::string File::getLongFileName() const { + return filename_long; +} + +bool File::isVersioned() const noexcept { + return !version.empty(); +} + +std::string File::getDirectory() const { + return directory; +} diff --git a/installer/File.h b/installer/File.h index f070ab3a9a5..604af0c6966 100644 --- a/installer/File.h +++ b/installer/File.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -23,7 +23,8 @@ class File : public Record { public: - explicit File(const nlohmann::json& json, const std::string& component); + explicit File(const nlohmann::json& json, const std::string& component, + const std::string& directory); ~File() = default; MSIHANDLE getRecord() const override; std::filesystem::path getFilepath() const; @@ -35,10 +36,16 @@ class File : public Record { void setAttributes(int a) noexcept; void setSequence(int s) noexcept; void setFilepath(const std::filesystem::path& p); + void setShortFileName(const std::string& n); + void setLongFileName(const std::string& n); + std::string getShortFileName() const; + std::string getLongFileName() const; + bool isVersioned() const noexcept; + std::string getDirectory() const; private: - std::string file{}; std::string component{}; - std::string filename{}; + std::string filename_short{}; + std::string filename_long{}; int filesize = MSI_NULL_INTEGER; std::string version{}; std::string language{}; @@ -46,4 +53,5 @@ class File : public Record { int sequence = MSI_NULL_INTEGER; std::filesystem::path filepath{}; bool isFont = false; + std::string directory{}; }; diff --git a/installer/FileTable.cpp b/installer/FileTable.cpp index 2e66722f379..ab45a2c6e8c 100644 --- a/installer/FileTable.cpp +++ b/installer/FileTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -16,6 +16,7 @@ // along with BOINC. If not, see . #include +#include #include "FileTable.h" #include "CabHelper.h" @@ -119,12 +120,56 @@ std::filesystem::path FileTable::GetAbsolutePath( return root_path / p; } +std::tuple FileTable::GetFileName( + const std::filesystem::path& filePath, const std::string& directory) { + auto extension = filePath.extension().string(); + if (extension.size() > 4) { + extension = extension.substr(0, 4); + } + std::transform(extension.begin(), extension.end(), extension.begin(), + ::toupper); + auto name = filePath.filename().stem().string(); + std::transform(name.begin(), name.end(), name.begin(), ::toupper); + auto i = 0; + const auto filename_long = name; + while (true) { + auto suffix_len = 1; + auto t = i; + while (t /= 10) { + suffix_len++; + } + ++suffix_len; + if (suffix_len > 6) { + suffix_len = 6; + } + if (filename_long.size() > 8) { + name = filename_long.substr(0, 8 - suffix_len) + "~" + + std::to_string(++i); + std::transform(name.begin(), name.end(), name.begin(), ::toupper); + } + auto found = false; + for (const auto& file : files) { + if (file.getDirectory() == directory && + file.getShortFileName() == name + extension) { + found = true; + break; + } + } + if (!found) { + break; + } + } + + return { name + extension, filePath.filename().string() }; +} + FileTable::FileTable(const std::vector& directories, const std::filesystem::path& root_path, const std::filesystem::path& output_path, const std::string& platform, - const std::string& configuration) : root_path(root_path), + const std::string& configuration, + std::shared_ptr validationTable) : root_path(root_path), output_path(output_path), platform(platform), - configuration(configuration) { + configuration(configuration), validationTable(validationTable) { int sequence = 0; for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { @@ -144,10 +189,122 @@ FileTable::FileTable(const std::vector& directories, } file.setFilesize(static_cast( GetFileSize(file.getFilepath().string()))); + auto [filename_short, filename_long] = + GetFileName(file.getFilepath(), + component.getDirectory()); + file.setShortFileName(filename_short); + file.setLongFileName(filename_long); files.push_back(file); } } } + + const auto tableName = std::string("File"); + const auto url = std::string("https://learn.microsoft.com/en-us/windows/win32/msi/file-table"); + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "File", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("A non-localized token that uniquely " + "identifies the file.", url) + )); + validationTable->add(Validation( + tableName, + "Component_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Component", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The external key into the first column of the " + "Component Table.", url) + )); + validationTable->add(Validation( + tableName, + "FileName", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFilename, + "", + DescriptionWithUrl("The file name used for installation.", url) + )); + validationTable->add(Validation( + tableName, + "FileSize", + false, + 0, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The size of the file in bytes.", url) + )); + validationTable->add(Validation( + tableName, + "Version", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "File", + 1, + ValidationCategoryVersion, + "", + DescriptionWithUrl("This field is the version string for a " + "versioned file.", url) + )); + validationTable->add(Validation( + tableName, + "Language", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryLanguage, + "", + DescriptionWithUrl("A list of decimal language IDs separated by " + "commas.", url) + )); + validationTable->add(Validation( + tableName, + "Attributes", + true, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The integer that contains bit flags that " + "represent file attributes.", url) + )); + validationTable->add(Validation( + tableName, + "Sequence", + false, + 1, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Sequence position of this file on the media " + "images.", url) + )); + } } bool FileTable::generate(MSIHANDLE hDatabase) { @@ -165,11 +322,11 @@ bool FileTable::generate(MSIHANDLE hDatabase) { std::filesystem::remove(output_path / cabname); if (!MediaTable({ Media(1, static_cast(files.size()), "1", - "#" + cabname, "DISK1", "")}).generate(hDatabase)) { + "#" + cabname, "DISK1", "") }, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate MediaTable" << std::endl; return false; } - if (!MsiFileHashTable(files).generate(hDatabase)) { + if (!MsiFileHashTable(files, validationTable).generate(hDatabase)) { std::cerr << "Failed to generate MsiFileHashTable" << std::endl; return false; } diff --git a/installer/FileTable.h b/installer/FileTable.h index 4c4caeed22b..41dccbe5fe7 100644 --- a/installer/FileTable.h +++ b/installer/FileTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,13 +20,15 @@ #include "Generator.h" #include "Directory.h" #include "File.h" +#include "ValidationTable.h" class FileTable : public Generator { public: explicit FileTable(const std::vector& directories, const std::filesystem::path& root_path, const std::filesystem::path& output_path, const std::string& platform, - const std::string& configuration); + const std::string& configuration, + std::shared_ptr validationTable); ~FileTable() = default; bool generate(MSIHANDLE hDatabase) override; private: @@ -35,9 +37,12 @@ class FileTable : public Generator { size_t GetFileSize(const std::string& filePath); std::filesystem::path GetAbsolutePath( const std::filesystem::path& filePath); + std::tuple GetFileName( + const std::filesystem::path& filePath, const std::string& directory); std::vector files{}; std::filesystem::path root_path{}; std::filesystem::path output_path{}; std::string platform{}; std::string configuration{}; + std::shared_ptr validationTable; }; diff --git a/installer/FontTable.cpp b/installer/FontTable.cpp index 8e128ccbc2d..6fe129302c4 100644 --- a/installer/FontTable.cpp +++ b/installer/FontTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,7 +17,8 @@ #include "FontTable.h" -FontTable::FontTable(const std::vector& directories) { +FontTable::FontTable(const std::vector& directories, + std::shared_ptr validationTable) { for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { for (const auto& file : component.getFiles()) { @@ -27,6 +28,36 @@ FontTable::FontTable(const std::vector& directories) { } } } + + const auto tableName = std::string("Font"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/font-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "File_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "File", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("External key into the File table entry for " + "the font file.", url) + )); + validationTable->add(Validation( + tableName, + "FontTitle", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("Font name.", url) + )); + } } bool FontTable::generate(MSIHANDLE database) { diff --git a/installer/FontTable.h b/installer/FontTable.h index ffd589ad4c0..6b38f58e8b4 100644 --- a/installer/FontTable.h +++ b/installer/FontTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "Generator.h" #include "Font.h" #include "Directory.h" +#include "ValidationTable.h" class FontTable : public Generator { public: - explicit FontTable(const std::vector& directories); + explicit FontTable(const std::vector& directories, + std::shared_ptr validationTable); ~FontTable() = default; bool generate(MSIHANDLE database) override; private: diff --git a/installer/IconTable.cpp b/installer/IconTable.cpp index 1950e3108fd..3de033693dc 100644 --- a/installer/IconTable.cpp +++ b/installer/IconTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,12 +19,43 @@ IconTable::IconTable(const nlohmann::json& json, const std::filesystem::path& path, const std::string& platform, - const std::string& configuration) { + const std::string& configuration, + std::shared_ptr validationTable) { std::cout << "Loading IconTable..." << std::endl; for (const auto& item : json) { values.emplace_back(item, path, platform, configuration); } + + const auto tableName = std::string("Icon"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/icon-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Name", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of the icon file.", url) + )); + validationTable->add(Validation( + tableName, + "Data", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryBinary, + "", + DescriptionWithUrl("The binary icon data in PE (.dll or .exe) or " + "icon (.ico) format.", url) + )); + } } bool IconTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/IconTable.h b/installer/IconTable.h index 3a2ce21bca9..09827c6df4e 100644 --- a/installer/IconTable.h +++ b/installer/IconTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,12 +19,14 @@ #include "Icon.h" #include "Generator.h" +#include "ValidationTable.h" class IconTable : public Generator { public: explicit IconTable(const nlohmann::json& json, const std::filesystem::path& path, const std::string& platform, - const std::string& configuration); + const std::string& configuration, + std::shared_ptr validationTable); ~IconTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/InstallExecuteSequenceTable.cpp b/installer/InstallExecuteSequenceTable.cpp index b91f937ec87..424438562bb 100644 --- a/installer/InstallExecuteSequenceTable.cpp +++ b/installer/InstallExecuteSequenceTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -18,12 +18,56 @@ #include "InstallExecuteSequenceTable.h" InstallExecuteSequenceTable::InstallExecuteSequenceTable( - const nlohmann::json& json) { + const nlohmann::json& json, + std::shared_ptr validationTable) { std::cout << "Loading InstallExecuteSequenceTable..." << std::endl; for (const auto& value : json) { actions.emplace_back(value); } + + const auto tableName = std::string("InstallExecuteSequence"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/installexecutesequence-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Action", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of the action to execute.", url) + )); + validationTable->add(Validation( + tableName, + "Condition", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCondition, + "", + DescriptionWithUrl("This field contains a conditional expression.", + url) + )); + validationTable->add(Validation( + tableName, + "Sequence", + true, + -4, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Number that determines the sequence position " + "in which this action is to be executed.", url) + )); + } } bool InstallExecuteSequenceTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/InstallExecuteSequenceTable.h b/installer/InstallExecuteSequenceTable.h index 421892461ea..e8fb5a88dcb 100644 --- a/installer/InstallExecuteSequenceTable.h +++ b/installer/InstallExecuteSequenceTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,10 +19,12 @@ #include "Action.h" #include "Generator.h" +#include "ValidationTable.h" class InstallExecuteSequenceTable : public Generator { public: - explicit InstallExecuteSequenceTable(const nlohmann::json& json); + explicit InstallExecuteSequenceTable(const nlohmann::json& json, + std::shared_ptr validationTable); ~InstallExecuteSequenceTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/InstallUISequenceTable.cpp b/installer/InstallUISequenceTable.cpp index bc8ce8609e0..5fe2b19470b 100644 --- a/installer/InstallUISequenceTable.cpp +++ b/installer/InstallUISequenceTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,12 +17,56 @@ #include "InstallUISequenceTable.h" -InstallUISequenceTable::InstallUISequenceTable(const nlohmann::json& json) { +InstallUISequenceTable::InstallUISequenceTable(const nlohmann::json& json, + std::shared_ptr validationTable) { std::cout << "Loading InstallUISequenceTable..." << std::endl; for (const auto& value : json) { actions.emplace_back(value); } + + const auto tableName = std::string("InstallUISequence"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/installuisequence-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Action", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of the action to execute.", url) + )); + validationTable->add(Validation( + tableName, + "Condition", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCondition, + "", + DescriptionWithUrl("This field contains a conditional expression.", + url) + )); + validationTable->add(Validation( + tableName, + "Sequence", + true, + -4, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The number in this column determines the " + "sequence position in which this action is run.", url) + )); + } } bool InstallUISequenceTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/InstallUISequenceTable.h b/installer/InstallUISequenceTable.h index d7734a0ab95..3b7042c3a5d 100644 --- a/installer/InstallUISequenceTable.h +++ b/installer/InstallUISequenceTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,10 +19,12 @@ #include "Action.h" #include "Generator.h" +#include "ValidationTable.h" class InstallUISequenceTable : public Generator { public: - explicit InstallUISequenceTable(const nlohmann::json& json); + explicit InstallUISequenceTable(const nlohmann::json& json, + std::shared_ptr validationTable); ~InstallUISequenceTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/Installer.cpp b/installer/Installer.cpp index 10c177e985e..98c83eb2407 100644 --- a/installer/Installer.cpp +++ b/installer/Installer.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -36,14 +36,44 @@ #include "InstallExecuteSequenceTable.h" #include "InstallUISequenceTable.h" #include "LaunchConditionTable.h" +#include "ListboxTable.h" #include "PropertyTable.h" #include "RadioButtonTable.h" #include "TextStyleTable.h" #include "UITextTable.h" #include "UpgradeTable.h" +#include "ValidationTable.h" #include "Installer.h" +#if defined(_DEBUG) && !defined(MSI_VALIDATE) +#define MSI_VALIDATE +#endif + +const auto ActionTextTableName = std::string("ActionText"); +const auto AdminExecuteSequenceTableName = std::string("AdminExecuteSequence"); +const auto AdminUISequenceTableName = std::string("AdminUISequence"); +const auto AdvtExecuteSequenceTableName = std::string("AdvtExecuteSequence"); +const auto BinaryTableName = std::string("Binary"); +const auto CheckboxTableName = std::string("Checkbox"); +const auto CustomActionTableName = std::string("CustomAction"); +const auto DialogTableName = std::string("Dialog"); +const auto DirectoryTableName = std::string("Directory"); +const auto ErrorTableName = std::string("Error"); +const auto FeatureTableName = std::string("Feature"); +const auto IconTableName = std::string("Icon"); +const auto InstallExecuteSequenceTableName = + std::string("InstallExecuteSequence"); +const auto InstallUISequenceTableName = std::string("InstallUISequence"); +const auto LaunchConditionTableName = std::string("LaunchCondition"); +const auto ListboxTableName = std::string("Listbox"); +const auto PropertyTableName = std::string("Property"); +const auto SummaryTableName = std::string("Summary"); +const auto TextStyleTableName = std::string("TextStyle"); +const auto UITextTableName = std::string("UIText"); +const auto UpgradeTableName = std::string("Upgrade"); +const auto ValidationTableName = std::string("Validation"); + Installer::Installer(const std::filesystem::path& output_path, const std::string& platform, const std::string& configuration) : output_path(output_path), platform(platform), @@ -78,90 +108,113 @@ bool Installer::load_from_json(const nlohmann::json& json, return false; } } - if (JsonHelper::exists(json, "Summary")) { - tables["Summary"] = std::make_shared( - json["Summary"], installer_strings, platform); + // should always be first + std::shared_ptr validationTable = +#ifndef MSI_VALIDATE + nullptr; +#else + std::make_shared(); + tables[ValidationTableName] = validationTable; +#endif + if (JsonHelper::exists(json, SummaryTableName)) { + tables[SummaryTableName] = + std::make_shared( + json[SummaryTableName], installer_strings, platform); } - if (JsonHelper::exists(json, "ActionText")) { - tables["ActionText"] = std::make_shared( - json["ActionText"], installer_strings); + if (JsonHelper::exists(json, ActionTextTableName)) { + tables[ActionTextTableName] = std::make_shared( + json[ActionTextTableName], installer_strings, validationTable); } - if (JsonHelper::exists(json, "AdminExecuteSequence")) { - tables["AdminExecuteSequence"] = + if (JsonHelper::exists(json, AdminExecuteSequenceTableName)) { + tables[AdminExecuteSequenceTableName] = std::make_shared( - json["AdminExecuteSequence"]); + json[AdminExecuteSequenceTableName], validationTable); } - if (JsonHelper::exists(json, "AdminUISequence")) { - tables["AdminUISequence"] = std::make_shared( - json["AdminUISequence"]); + if (JsonHelper::exists(json, AdminUISequenceTableName)) { + tables[AdminUISequenceTableName] = + std::make_shared( + json[AdminUISequenceTableName], validationTable); } - if (JsonHelper::exists(json, "AdvtExecuteSequence")) { - tables["AdvtExecuteSequence"] = + if (JsonHelper::exists(json, AdvtExecuteSequenceTableName)) { + tables[AdvtExecuteSequenceTableName] = std::make_shared( - json["AdvtExecuteSequence"]); + json[AdvtExecuteSequenceTableName], validationTable); } - if (JsonHelper::exists(json, "Binary")) { - tables["Binary"] = std::make_shared( - json["Binary"], path, platform, configuration); + if (JsonHelper::exists(json, BinaryTableName)) { + tables[BinaryTableName] = std::make_shared( + json[BinaryTableName], path, platform, configuration, + validationTable); } - if (JsonHelper::exists(json, "Checkbox")) { - tables["Checkbox"] = std::make_shared( - json["Checkbox"]); + if (JsonHelper::exists(json, CheckboxTableName)) { + tables[CheckboxTableName] = std::make_shared( + json[CheckboxTableName], validationTable); } - if (JsonHelper::exists(json, "CustomAction")) { - tables["CustomAction"] = std::make_shared( - json["CustomAction"]); + if (JsonHelper::exists(json, CustomActionTableName)) { + tables[CustomActionTableName] = + std::make_shared( + json[CustomActionTableName], validationTable); } - if (JsonHelper::exists(json, "Dialog")) { - tables["Dialog"] = std::make_shared(json["Dialog"], - installer_strings); + if (JsonHelper::exists(json, DialogTableName)) { + tables[DialogTableName] = + std::make_shared(json[DialogTableName], + installer_strings, validationTable); } - if (JsonHelper::exists(json, "Directory")) { - tables["Directory"] = std::make_shared( - json["Directory"], path, output_path, installer_strings, - platform, configuration); + if (JsonHelper::exists(json, DirectoryTableName)) { + tables[DirectoryTableName] = std::make_shared( + json[DirectoryTableName], path, output_path, installer_strings, + platform, configuration, validationTable); } - if (JsonHelper::exists(json, "Error")) { - tables["Error"] = std::make_shared(json["Error"], - installer_strings); + if (JsonHelper::exists(json, ErrorTableName)) { + tables[ErrorTableName] = + std::make_shared(json[ErrorTableName], + installer_strings, validationTable); } - if (JsonHelper::exists(json, "Feature")) { - tables["Feature"] = std::make_shared(json["Feature"], - installer_strings); + if (JsonHelper::exists(json, FeatureTableName)) { + tables[FeatureTableName] = + std::make_shared(json[FeatureTableName], + installer_strings, validationTable); } - if (JsonHelper::exists(json, "Icon")) { - tables["Icon"] = std::make_shared(json["Icon"], path, - platform, configuration); + if (JsonHelper::exists(json, IconTableName)) { + tables[IconTableName] = + std::make_shared(json[IconTableName], path, + platform, configuration, validationTable); } - if (JsonHelper::exists(json, "InstallExecuteSequence")) { - tables["InstallExecuteSequence"] = + if (JsonHelper::exists(json, InstallExecuteSequenceTableName)) { + tables[InstallExecuteSequenceTableName] = std::make_shared( - json["InstallExecuteSequence"]); + json[InstallExecuteSequenceTableName], validationTable); } - if (JsonHelper::exists(json, "InstallUISequence")) { - tables["InstallUISequence"] = + if (JsonHelper::exists(json, InstallUISequenceTableName)) { + tables[InstallUISequenceTableName] = std::make_shared( - json["InstallUISequence"]); + json[InstallUISequenceTableName], validationTable); } - if (JsonHelper::exists(json, "LaunchCondition")) { - tables["LaunchCondition"] = std::make_shared( - json["LaunchCondition"], installer_strings); + if (JsonHelper::exists(json, LaunchConditionTableName)) { + tables[LaunchConditionTableName] = + std::make_shared( + json[LaunchConditionTableName], installer_strings, + validationTable); } - if (JsonHelper::exists(json, "Property")) { - tables["Property"] = std::make_shared( - json["Property"], installer_strings); + if (JsonHelper::exists(json, ListboxTableName)) { + tables[ListboxTableName] = std::make_shared( + json[ListboxTableName], installer_strings, validationTable); } - if (JsonHelper::exists(json, "TextStyle")) { - tables["TextStyle"] = std::make_shared( - json["TextStyle"]); + if (JsonHelper::exists(json, PropertyTableName)) { + tables[PropertyTableName] = std::make_shared( + json[PropertyTableName], installer_strings, validationTable); } - if (JsonHelper::exists(json, "UIText")) { - tables["UIText"] = std::make_shared(json["UIText"], - installer_strings); + if (JsonHelper::exists(json, TextStyleTableName)) { + tables[TextStyleTableName] = std::make_shared( + json[TextStyleTableName], validationTable); } - if (JsonHelper::exists(json, "Upgrade")) { - tables["Upgrade"] = std::make_shared( - json["Upgrade"]); + if (JsonHelper::exists(json, UITextTableName)) { + tables[UITextTableName] = + std::make_shared(json[UITextTableName], + installer_strings, validationTable); + } + if (JsonHelper::exists(json, UpgradeTableName)) { + tables[UpgradeTableName] = std::make_shared( + json[UpgradeTableName], validationTable); } } catch (const std::exception& e) { @@ -210,6 +263,9 @@ bool Installer::create_msi(const std::filesystem::path& msi) { } for (const auto& table : tables) { + if (table.first == ValidationTableName) { + continue; + } if (!table.second->generate(hDatabase)) { std::cerr << "Failed to write table " << table.first << std::endl; @@ -217,6 +273,15 @@ bool Installer::create_msi(const std::filesystem::path& msi) { } } + // should always be last + if (tables.find(ValidationTableName) != tables.end() && + tables[ValidationTableName] != nullptr) { + if (!tables[ValidationTableName]->generate(hDatabase)) { + std::cerr << "Failed to write table Validation" << std::endl; + return false; + } + } + result = MsiDatabaseCommit(hDatabase); if (result != ERROR_SUCCESS) { std::cerr << "MsiDatabaseCommit failed: " << result << std::endl; diff --git a/installer/LaunchConditionTable.cpp b/installer/LaunchConditionTable.cpp index 5fd55e2db2f..e70dd4b01e4 100644 --- a/installer/LaunchConditionTable.cpp +++ b/installer/LaunchConditionTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -18,12 +18,45 @@ #include "LaunchConditionTable.h" LaunchConditionTable::LaunchConditionTable(const nlohmann::json& json, - InstallerStrings& installerStrings) { + InstallerStrings& installerStrings, + std::shared_ptr validationTable) { std::cout << "Loading LaunchConditionTable..." << std::endl; for (const auto& launchCondition : json) { launchConditions.emplace_back(launchCondition, installerStrings); } + + const auto tableName = std::string("LaunchCondition"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/launchcondition-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Condition", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCondition, + "", + DescriptionWithUrl("Expression that must evaluate to True for " + "installation to begin.", url) + )); + validationTable->add(Validation( + tableName, + "Description", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("Localizable text to display when the " + "condition fails and the installation must be terminated.", + url) + )); + } } bool LaunchConditionTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/LaunchConditionTable.h b/installer/LaunchConditionTable.h index 0733c8a91b9..eb05b2f9f67 100644 --- a/installer/LaunchConditionTable.h +++ b/installer/LaunchConditionTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,11 +19,13 @@ #include "Generator.h" #include "LaunchCondition.h" +#include "ValidationTable.h" class LaunchConditionTable : public Generator { public: explicit LaunchConditionTable(const nlohmann::json& json, - InstallerStrings& installerStrings); + InstallerStrings& installerStrings, + std::shared_ptr validationTable); ~LaunchConditionTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/Listbox.cpp b/installer/Listbox.cpp new file mode 100644 index 00000000000..97d6f202a75 --- /dev/null +++ b/installer/Listbox.cpp @@ -0,0 +1,32 @@ +// This file is part of BOINC. +// https://boinc.berkeley.edu +// Copyright (C) 2025 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +#include "Listbox.h" +#include "MsiHelper.h" +#include "JsonHelper.h" + +Listbox::Listbox(const nlohmann::json& json, + InstallerStrings& installerStrings) { + JsonHelper::get(json, "Property", property); + JsonHelper::get(json, "Order", order); + JsonHelper::get(json, "Value", value); + JsonHelper::get(json, "Text", text, installerStrings); +} + +MSIHANDLE Listbox::getRecord() const { + return MsiHelper::MsiRecordSet({ property, order, value, text }); +} diff --git a/installer/Listbox.h b/installer/Listbox.h new file mode 100644 index 00000000000..3d0e616b389 --- /dev/null +++ b/installer/Listbox.h @@ -0,0 +1,36 @@ +// This file is part of BOINC. +// https://boinc.berkeley.edu +// Copyright (C) 2025 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +#pragma once + +#include + +#include "Record.h" +#include "InstallerStrings.h" + +class Listbox : public Record { +public: + explicit Listbox(const nlohmann::json& json, + InstallerStrings& installerStrings); + ~Listbox() = default; + MSIHANDLE getRecord() const override; +private: + std::string property{}; + int order = MSI_NULL_INTEGER; + std::string value{}; + std::string text{}; +}; diff --git a/installer/ListboxTable.cpp b/installer/ListboxTable.cpp new file mode 100644 index 00000000000..db1db09b067 --- /dev/null +++ b/installer/ListboxTable.cpp @@ -0,0 +1,99 @@ +// This file is part of BOINC. +// https://boinc.berkeley.edu +// Copyright (C) 2025 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +#include + +#include "ListboxTable.h" + +ListboxTable::ListboxTable(const nlohmann::json& json, + InstallerStrings& installerStrings, + std::shared_ptr validationTable) { + std::cout << "Loading ListboxTable..." << std::endl; + for (const auto& item : json) { + values.emplace_back(item, installerStrings); + } + + const auto tableName = std::string("ListBox"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/listbox-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Property", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("A named property to be tied to this item.", + url) + )); + validationTable->add(Validation( + tableName, + "Order", + false, + 1, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("A positive integer used to determine the " + "ordering of the items that appear in a single list box.", url) + )); + validationTable->add(Validation( + tableName, + "Value", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("The value string associated with this item.", + url) + )); + validationTable->add(Validation( + tableName, + "Text", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("The localizable, visible text to be assigned " + "to the item.", url) + )); + } +} + +bool ListboxTable::generate(MSIHANDLE hDatabase) { + std::cout << "Generating ListboxTable..." << std::endl; + + const auto sql_create = "CREATE TABLE `ListBox` " + "(`Property` CHAR(72) NOT NULL, `Order` SHORT NOT NULL, " + "`Value` CHAR(64) NOT NULL, `Text` CHAR(64) " + "PRIMARY KEY `Property`, `Order`)"; + const auto sql_insert = "INSERT INTO `ListBox` " + "(`Property`, `Order`, `Value`, `Text`) VALUES (?, ?, ?, ?)"; + + return Generator::generate(hDatabase, sql_create, sql_insert, values); +} diff --git a/installer/ListboxTable.h b/installer/ListboxTable.h new file mode 100644 index 00000000000..496821ca51b --- /dev/null +++ b/installer/ListboxTable.h @@ -0,0 +1,34 @@ +// This file is part of BOINC. +// https://boinc.berkeley.edu +// Copyright (C) 2025 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +#pragma once + +#include "Generator.h" +#include "InstallerStrings.h" +#include "Listbox.h" +#include "ValidationTable.h" + +class ListboxTable : public Generator { +public: + explicit ListboxTable(const nlohmann::json& json, + InstallerStrings& installerStrings, + std::shared_ptr validationTable); + ~ListboxTable() = default; + bool generate(MSIHANDLE hDatabase) override; +private: + std::vector values{}; +}; diff --git a/installer/MediaTable.cpp b/installer/MediaTable.cpp index e7b57ea470c..70aa0e8a0a3 100644 --- a/installer/MediaTable.cpp +++ b/installer/MediaTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,7 +17,89 @@ #include "MediaTable.h" -MediaTable::MediaTable(const std::vector& media) : media(media) { +MediaTable::MediaTable(const std::vector& media, + std::shared_ptr validationTable) : media(media) { + const auto tableName = std::string("Media"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/media-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "DiskId", + false, + 1, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Determines the sort order for the table.", url) + )); + validationTable->add(Validation( + tableName, + "LastSequence", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("File sequence number for the last file for " + "this media.", url) + )); + validationTable->add(Validation( + tableName, + "DiskPrompt", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("The disk name, which is usually the visible " + "text printed on the disk.", url) + )); + validationTable->add(Validation( + tableName, + "Cabinet", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryCabinet, + "", + DescriptionWithUrl("The name of the cabinet if some or all of the " + "files stored on the media are compressed into a cabinet " + "file.", url) + )); + validationTable->add(Validation( + tableName, + "VolumeLabel", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("The label attributed to the volume.", url) + )); + validationTable->add(Validation( + tableName, + "Source", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryProperty, + "", + DescriptionWithUrl("This field is only used by patching and is " + "otherwise left blank.", url) + )); + } } bool MediaTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/MediaTable.h b/installer/MediaTable.h index aedbf48c645..e2d4b57f977 100644 --- a/installer/MediaTable.h +++ b/installer/MediaTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,10 +19,12 @@ #include "Generator.h" #include "Media.h" +#include "ValidationTable.h" class MediaTable : public Generator { public: - explicit MediaTable(const std::vector& media); + explicit MediaTable(const std::vector& media, + std::shared_ptr validationTable); ~MediaTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/MsiFileHashTable.cpp b/installer/MsiFileHashTable.cpp index 3b8a3298231..7155d1f9f41 100644 --- a/installer/MsiFileHashTable.cpp +++ b/installer/MsiFileHashTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,8 +17,12 @@ #include "MsiFileHashTable.h" -MsiFileHashTable::MsiFileHashTable(const std::vector& files) { +MsiFileHashTable::MsiFileHashTable(const std::vector& files, + std::shared_ptr validationTable) { for (const auto& file : files) { + if (file.isVersioned()) { + continue; + } MSIFILEHASHINFO hashInfo; hashInfo.dwFileHashInfoSize = sizeof(MSIFILEHASHINFO); if (MsiGetFileHash(file.getFilepath().string().c_str(), 0, &hashInfo) @@ -30,6 +34,84 @@ MsiFileHashTable::MsiFileHashTable(const std::vector& files) { msiFileHashes.emplace_back(file.getFileId(), hashInfo.dwData[0], hashInfo.dwData[1], hashInfo.dwData[2], hashInfo.dwData[3]); } + + const auto tableName = std::string("MsiFileHash"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/msifilehash-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "File_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "File", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Foreign key to File table.", url) + )); + validationTable->add(Validation( + tableName, + "Options", + false, + 0, + 0, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("This column must be 0 and is reserved for " + "future use.", url) + )); + validationTable->add(Validation( + tableName, + "HashPart1", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("First 32 bits of hash.", url) + )); + validationTable->add(Validation( + tableName, + "HashPart2", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Second 32 bits of hash.", url) + )); + validationTable->add(Validation( + tableName, + "HashPart3", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Third 32 bits of hash.", url) + )); + validationTable->add(Validation( + tableName, + "HashPart4", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Fourth 32 bits of hash.", url) + )); + } } bool MsiFileHashTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/MsiFileHashTable.h b/installer/MsiFileHashTable.h index 7739e3f4cb4..d1a70b5cccd 100644 --- a/installer/MsiFileHashTable.h +++ b/installer/MsiFileHashTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "Generator.h" #include "MsiFileHash.h" #include "File.h" +#include "ValidationTable.h" class MsiFileHashTable : public Generator { public: - explicit MsiFileHashTable(const std::vector& files); + explicit MsiFileHashTable(const std::vector& files, + std::shared_ptr validationTable); ~MsiFileHashTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/PropertyTable.cpp b/installer/PropertyTable.cpp index 38176300afd..9311b96dc8e 100644 --- a/installer/PropertyTable.cpp +++ b/installer/PropertyTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,13 +19,44 @@ #include "GuidHelper.h" PropertyTable::PropertyTable(const nlohmann::json& json, - InstallerStrings& installerStrings) { + InstallerStrings& installerStrings, + std::shared_ptr validationTable) { std::cout << "Loading PropertyTable..." << std::endl; for (const auto& item : json) { properties.emplace_back(item, installerStrings); } std::cout << "Generating new ProductCode..." << std::endl; properties.emplace_back("ProductCode", GuidHelper::generate_guid()); + + const auto tableName = std::string("Property"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/property-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Property", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The name of the property.", url) + )); + validationTable->add(Validation( + tableName, + "Value", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("A localizable string value for the property.", + url) + )); + } } bool PropertyTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/PropertyTable.h b/installer/PropertyTable.h index faaf05cca84..e536bae2bd2 100644 --- a/installer/PropertyTable.h +++ b/installer/PropertyTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,11 +19,13 @@ #include "Generator.h" #include "Property.h" +#include "ValidationTable.h" class PropertyTable : public Generator { public: explicit PropertyTable(const nlohmann::json& json, - InstallerStrings& installerStrings); + InstallerStrings& installerStrings, + std::shared_ptr validationTable); ~PropertyTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/RadioButtonTable.cpp b/installer/RadioButtonTable.cpp index 8ee1afd4103..527d4d2c3e6 100644 --- a/installer/RadioButtonTable.cpp +++ b/installer/RadioButtonTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,12 +17,134 @@ #include "RadioButtonTable.h" -RadioButtonTable::RadioButtonTable(const std::vector& controls) { +RadioButtonTable::RadioButtonTable(const std::vector& controls, + std::shared_ptr validationTable) { for (const auto& control : controls) { for (const auto& radioButton : control.get_radio_buttons()) { properties.emplace_back(radioButton); } } + + const auto tableName = std::string("RadioButton"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/radiobutton-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Property", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("A named property to be tied to this radio " + "button.", url) + )); + validationTable->add(Validation( + tableName, + "Order", + false, + 1, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("A positive integer used to determine the " + "ordering of the items within one list.", url) + )); + validationTable->add(Validation( + tableName, + "Value", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("The value string associated with this button.", + url) + )); + validationTable->add(Validation( + tableName, + "X", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The horizontal coordinate within the group of " + "the upper-left corner of the bounding rectangle of the radio " + "button.", url) + )); + validationTable->add(Validation( + tableName, + "Y", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The vertical coordinate within the group of " + "the upper-left corner of the bounding rectangle of the radio " + "button.", url) + )); + validationTable->add(Validation( + tableName, + "Width", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The width of the button.", url) + )); + validationTable->add(Validation( + tableName, + "Height", + false, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The height of the button.", url) + )); + validationTable->add(Validation( + tableName, + "Text", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("The localizable, visible title to be assigned " + "to the radio button.", url) + )); + validationTable->add(Validation( + tableName, + "Help", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("The Help strings used with the button.", url) + )); + } } bool RadioButtonTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/RadioButtonTable.h b/installer/RadioButtonTable.h index 7a5a118eed0..c50e2ef6d04 100644 --- a/installer/RadioButtonTable.h +++ b/installer/RadioButtonTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -21,10 +21,12 @@ #include "RadioButton.h" #include "Control.h" #include "InstallerStrings.h" +#include "ValidationTable.h" class RadioButtonTable : public Generator { public: - explicit RadioButtonTable(const std::vector& controls); + explicit RadioButtonTable(const std::vector& controls, + std::shared_ptr validationTable); ~RadioButtonTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/RegistryTable.cpp b/installer/RegistryTable.cpp index cd31e8c35f6..246e9cb4e3b 100644 --- a/installer/RegistryTable.cpp +++ b/installer/RegistryTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,7 +17,8 @@ #include "RegistryTable.h" -RegistryTable::RegistryTable(const std::vector& directories) { +RegistryTable::RegistryTable(const std::vector& directories, + std::shared_ptr validationTable) { for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { for (const auto& registry : component.getRegistries()) { @@ -25,6 +26,90 @@ RegistryTable::RegistryTable(const std::vector& directories) { } } } + + const auto tableName = std::string("Registry"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/registry-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Registry", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Primary key used to identify a registry " + "record.", url) + )); + validationTable->add(Validation( + tableName, + "Root", + false, + -1, + 3, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("he predefined root key for the registry " + "value.", url) + )); + validationTable->add(Validation( + tableName, + "Key", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryRegPath, + "", + DescriptionWithUrl("The localizable key for the registry value.", + url) + )); + validationTable->add(Validation( + tableName, + "Name", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This column contains the registry value name " + "(localizable).", url) + )); + validationTable->add(Validation( + tableName, + "Value", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This column is the localizable registry " + "value.", url) + )); + validationTable->add(Validation( + tableName, + "Component_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Component", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("External key into the first column of the " + "Component table referencing the component that controls the " + "installation of the registry value.", url) + )); + } } bool RegistryTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/RegistryTable.h b/installer/RegistryTable.h index 7d7409c72cb..fd8b25361ee 100644 --- a/installer/RegistryTable.h +++ b/installer/RegistryTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "Generator.h" #include "Registry.h" #include "Directory.h" +#include "ValidationTable.h" class RegistryTable : public Generator { public: - explicit RegistryTable(const std::vector& directories); + explicit RegistryTable(const std::vector& directories, + std::shared_ptr validationTable); ~RegistryTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/RemoveFileTable.cpp b/installer/RemoveFileTable.cpp index bd8baf3b015..4fda3919d19 100644 --- a/installer/RemoveFileTable.cpp +++ b/installer/RemoveFileTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,13 +17,84 @@ #include "RemoveFileTable.h" -RemoveFileTable::RemoveFileTable(const std::vector& directories) { +RemoveFileTable::RemoveFileTable(const std::vector& directories, + std::shared_ptr validationTable) { for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { for (const auto& file : component.getRemoveFiles()) values.emplace_back(file); } } + + const auto tableName = std::string("RemoveFile"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/removefile-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "FileKey", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Primary key used to identify this particular " + "table entry.", url) + )); + validationTable->add(Validation( + tableName, + "Component_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Component", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("External key the first column of the " + "Component table.", url) + )); + validationTable->add(Validation( + tableName, + "FileName", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryWildCardFilename, + "", + DescriptionWithUrl("This column contains the localizable name of " + "the file to be removed.", url) + )); + validationTable->add(Validation( + tableName, + "DirProperty", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Name of a property whose value is assumed to " + "resolve to the full path to the folder of the file to be " + "removed.", url) + )); + validationTable->add(Validation( + tableName, + "InstallMode", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "1;2;3", + DescriptionWithUrl("Specifies the mode in which the file is installed.", url) + )); + } } bool RemoveFileTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/RemoveFileTable.h b/installer/RemoveFileTable.h index 944df78b836..909c2bc5540 100644 --- a/installer/RemoveFileTable.h +++ b/installer/RemoveFileTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "Generator.h" #include "RemoveFile.h" #include "Directory.h" +#include "ValidationTable.h" class RemoveFileTable : public Generator { public: - explicit RemoveFileTable(const std::vector& directories); + explicit RemoveFileTable(const std::vector& directories, + std::shared_ptr validationTable); ~RemoveFileTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/ServiceControlTable.cpp b/installer/ServiceControlTable.cpp index a1c56e89148..40459d221f6 100644 --- a/installer/ServiceControlTable.cpp +++ b/installer/ServiceControlTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -18,7 +18,8 @@ #include "ServiceControlTable.h" ServiceControlTable::ServiceControlTable( - const std::vector& directories) { + const std::vector& directories, + std::shared_ptr validationTable) { for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { for (const auto& serviceControl : component.getServiceControls()) { @@ -26,6 +27,89 @@ ServiceControlTable::ServiceControlTable( } } } + + const auto tableName = std::string("ServiceControl"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/servicecontrol-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "ServiceControl", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("This is the primary key of this table.", url) + )); + validationTable->add(Validation( + tableName, + "Name", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This column is the string naming the service.", + url) + )); + validationTable->add(Validation( + tableName, + "Event", + false, + 0, + 187, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("This column contains the operations to be " + "performed on the named service.", url) + )); + validationTable->add(Validation( + tableName, + "Arguments", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("A list of arguments for starting services.", + url) + )); + validationTable->add(Validation( + tableName, + "Wait", + true, + 0, + 1, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("Leaving this field null or entering a value " + "of 1 causes the installer to wait a maximum of 30 seconds " + "for the service to complete before proceeding.", url) + )); + validationTable->add(Validation( + tableName, + "Component_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Component", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("External key to column one of the Component " + "Table.", url) + )); + } } bool ServiceControlTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/ServiceControlTable.h b/installer/ServiceControlTable.h index 5570e5ab5ce..00b52e24b34 100644 --- a/installer/ServiceControlTable.h +++ b/installer/ServiceControlTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "ServiceControl.h" #include "Generator.h" #include "Directory.h" +#include "ValidationTable.h" class ServiceControlTable : public Generator { public: - explicit ServiceControlTable(const std::vector& directories); + explicit ServiceControlTable(const std::vector& directories, + std::shared_ptr validationTable); ~ServiceControlTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/ServiceInstallTable.cpp b/installer/ServiceInstallTable.cpp index 17ac28d315f..48a86f26a5a 100644 --- a/installer/ServiceInstallTable.cpp +++ b/installer/ServiceInstallTable.cpp @@ -1,7 +1,6 @@ -#include "ServiceInstallTable.h" // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,7 +18,8 @@ #include "ServiceInstallTable.h" ServiceInstallTable::ServiceInstallTable( - const std::vector& directories) { + const std::vector& directories, + std::shared_ptr validationTable) { for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { for (const auto& serviceInstall : component.getServiceInstalls()) { @@ -27,6 +27,182 @@ ServiceInstallTable::ServiceInstallTable( } } } + + const auto tableName = std::string("ServiceInstall"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/serviceinstall-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "ServiceInstall", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("This is the primary key for the table.", url) + )); + validationTable->add(Validation( + tableName, + "Name", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This column is the string that gives the " + "service name to install.", url) + )); + validationTable->add(Validation( + tableName, + "DisplayName", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This column is the localizable string that " + "user interface programs use to identify the service.", url) + )); + validationTable->add(Validation( + tableName, + "ServiceType", + false, + -2147483647, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("This column is a set of bit flags that " + "specify the type of service.", url) + )); + validationTable->add(Validation( + tableName, + "StartType", + false, + 0, + 4, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("This column is a set of bit flags that " + "specify when to start the service.", url) + )); + validationTable->add(Validation( + tableName, + "ErrorControl", + false, + -2147483647, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("This column specifies the action taken by the " + "startup program if the service fails to start during " + "startup.", url) + )); + validationTable->add(Validation( + tableName, + "LoadOrderGroup", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This column contains the string that names " + "the load ordering group of which this service is a member.", + url) + )); + validationTable->add(Validation( + tableName, + "Dependencies", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This column is a list of names of services or " + "load ordering groups that the system must start before this " + "service.", url) + )); + validationTable->add(Validation( + tableName, + "StartName", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("The service is logged on as the name given by " + "the string in this column.", url) + )); + validationTable->add(Validation( + tableName, + "Password", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This string is the password to the account " + "name specified in the StartName column.", url) + )); + validationTable->add(Validation( + tableName, + "Arguments", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This column contains any command line " + "arguments or properties required to run the service.", url) + )); + validationTable->add(Validation( + tableName, + "Component_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Component", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("External key to column one of the Component " + "Table.", url) + )); + validationTable->add(Validation( + tableName, + "Description", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This column contains a localizable " + "description for the service being configured.", url) + )); + } } bool ServiceInstallTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/ServiceInstallTable.h b/installer/ServiceInstallTable.h index ea5fd0719d4..fd3e87aaa76 100644 --- a/installer/ServiceInstallTable.h +++ b/installer/ServiceInstallTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "ServiceInstall.h" #include "Generator.h" #include "Directory.h" +#include "ValidationTable.h" class ServiceInstallTable : public Generator { public: - explicit ServiceInstallTable(const std::vector& directories); + explicit ServiceInstallTable(const std::vector& directories, + std::shared_ptr validationTable); ~ServiceInstallTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/ShortcutTable.cpp b/installer/ShortcutTable.cpp index 651c2c34345..f8d97420cc8 100644 --- a/installer/ShortcutTable.cpp +++ b/installer/ShortcutTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,7 +17,8 @@ #include "ShortcutTable.h" -ShortcutTable::ShortcutTable(const std::vector& directories) { +ShortcutTable::ShortcutTable(const std::vector& directories, + std::shared_ptr validationTable) { for (const auto& directory : directories) { for (const auto& component : directory.getComponents()) { for (const auto& shortcut : component.getShortcuts()) { @@ -25,6 +26,218 @@ ShortcutTable::ShortcutTable(const std::vector& directories) { } } } + + const auto tableName = std::string("Shortcut"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/shortcut-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Shortcut", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The key value for this table.", url) + )); + validationTable->add(Validation( + tableName, + "Directory_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Directory", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The external key into the first column of the " + "Directory table.", url) + )); + validationTable->add(Validation( + tableName, + "Name", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFilename, + "", + DescriptionWithUrl("The localizable name of the shortcut to be " + "created.", url) + )); + validationTable->add(Validation( + tableName, + "Component_", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Component", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The external key into the first column of the " + "Component table.", url) + )); + validationTable->add(Validation( + tableName, + "Target", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryShortcut, + "", + DescriptionWithUrl("The shortcut target.", url) + )); + validationTable->add(Validation( + tableName, + "Arguments", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("The command-line arguments for the shortcut.", + url) + )); + validationTable->add(Validation( + tableName, + "Description", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("The localizable description of the shortcut.", + url) + )); + validationTable->add(Validation( + tableName, + "Hotkey", + true, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The hotkey for the shortcut.", url) + )); + validationTable->add(Validation( + tableName, + "Icon_", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "Icon", + 1, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The external key to column one of the Icon " + "table.", url) + )); + validationTable->add(Validation( + tableName, + "IconIndex", + true, + -32767, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The icon index for the shortcut.", url) + )); + validationTable->add(Validation( + tableName, + "ShowCmd", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "1;3;7", + DescriptionWithUrl("The Show command for the application window.", + url) + )); + validationTable->add(Validation( + tableName, + "WkDir", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The name of the property that has the path of " + "the working directory for the shortcut.", url) + )); + validationTable->add(Validation( + tableName, + "DisplayResourceDll", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This field contains a Formatted string value " + "for the full path to the language-neutral portable " + "executable (LN file) that contains the resource " + "configuration (RC Config) data.", url) + )); + validationTable->add(Validation( + tableName, + "DisplayResourceId", + true, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The display name index for the shortcut.", url) + )); + validationTable->add(Validation( + tableName, + "DescriptionResourceDll", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("This field contains a Formatted string value " + "for the full path to the language-neutral portable " + "executable (LN file) that contains the resource " + "configuration (RC Config) data.", url) + )); + validationTable->add(Validation( + tableName, + "DescriptionResourceId", + true, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The description name index for the shortcut.", + url) + )); + } } bool ShortcutTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/ShortcutTable.h b/installer/ShortcutTable.h index 4bcc43796da..2ed10ffd528 100644 --- a/installer/ShortcutTable.h +++ b/installer/ShortcutTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -20,10 +20,12 @@ #include "Shortcut.h" #include "Directory.h" #include "Generator.h" +#include "ValidationTable.h" class ShortcutTable : public Generator { public: - explicit ShortcutTable(const std::vector& directories); + explicit ShortcutTable(const std::vector& directories, + std::shared_ptr validationTable); ~ShortcutTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/SummaryInformationTable.cpp b/installer/SummaryInformationTable.cpp index 48179bb9149..1979c10a7c2 100644 --- a/installer/SummaryInformationTable.cpp +++ b/installer/SummaryInformationTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -82,7 +82,7 @@ bool SummaryInformationTable::generate(MSIHANDLE hDatabase) { MSIHANDLE hSummaryInfo; const auto updateCount = summary.size(); - auto result =MsiGetSummaryInformation(hDatabase, nullptr, updateCount, + auto result = MsiGetSummaryInformation(hDatabase, nullptr, updateCount, &hSummaryInfo); if (result != ERROR_SUCCESS) { std::cerr << "MsiGetSummaryInformation failed: " << result diff --git a/installer/TextStyleTable.cpp b/installer/TextStyleTable.cpp index 7a2d669633d..8201bf4194f 100644 --- a/installer/TextStyleTable.cpp +++ b/installer/TextStyleTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,12 +17,82 @@ #include "TextStyleTable.h" -TextStyleTable::TextStyleTable(const nlohmann::json& json) { +TextStyleTable::TextStyleTable(const nlohmann::json& json, + std::shared_ptr validationTable) { std::cout << "Loading TextStyleTable..." << std::endl; for (const auto& textStyle : json) { textStyles.emplace_back(textStyle); } + + const auto tableName = std::string("TextStyle"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/textstyle-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "TextStyle", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("This column is the name of the font style.", + url) + )); + validationTable->add(Validation( + tableName, + "FaceName", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("A string indicating the name of the font.", + url) + )); + validationTable->add(Validation( + tableName, + "Size", + true, + 0, + 32767, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The font size measured in points.", url) + )); + validationTable->add(Validation( + tableName, + "Color", + true, + 0, + 16777215, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("This column specifies the text color " + "displayed by a Text Control.", url) + )); + validationTable->add(Validation( + tableName, + "StyleBits", + true, + 0, + 15, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("A combination of bits indicating the " + "formatting for the text.", url) + )); + } } bool TextStyleTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/TextStyleTable.h b/installer/TextStyleTable.h index be0de218f55..48f982b698b 100644 --- a/installer/TextStyleTable.h +++ b/installer/TextStyleTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,10 +19,12 @@ #include "Generator.h" #include "TextStyle.h" +#include "ValidationTable.h" class TextStyleTable : public Generator { public: - explicit TextStyleTable(const nlohmann::json& json); + explicit TextStyleTable(const nlohmann::json& json, + std::shared_ptr validationTable); ~TextStyleTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/UITextTable.cpp b/installer/UITextTable.cpp index a9f006065ab..38796e765c7 100644 --- a/installer/UITextTable.cpp +++ b/installer/UITextTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -18,11 +18,42 @@ #include "UITextTable.h" UITextTable::UITextTable(const nlohmann::json& json, - InstallerStrings& installerStrings) { + InstallerStrings& installerStrings, + std::shared_ptr validationTable) { std::cout << "Loading UITextTable.." << std::endl; for (const auto& item : json) { uiTexts.emplace_back(item, installerStrings); } + + const auto tableName = std::string("UIText"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/uitext-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "Key", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("A unique key that identifies the particular " + "string.", url) + )); + validationTable->add(Validation( + tableName, + "Text", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("The localized version of the string.", url) + )); + } } bool UITextTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/UITextTable.h b/installer/UITextTable.h index d639d03a59b..bdd1112db94 100644 --- a/installer/UITextTable.h +++ b/installer/UITextTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,11 +19,13 @@ #include "Generator.h" #include "UIText.h" +#include "ValidationTable.h" class UITextTable : public Generator { public: explicit UITextTable(const nlohmann::json& json, - InstallerStrings& installerStrings); + InstallerStrings& installerStrings, + std::shared_ptr validationTable); ~UITextTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/UpgradeTable.cpp b/installer/UpgradeTable.cpp index 058d9600356..94d6d30b53b 100644 --- a/installer/UpgradeTable.cpp +++ b/installer/UpgradeTable.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -17,12 +17,111 @@ #include "UpgradeTable.h" -UpgradeTable::UpgradeTable(const nlohmann::json& json) { +UpgradeTable::UpgradeTable(const nlohmann::json& json, + std::shared_ptr validationTable) { std::cout << "Loading UpgradeTable..." << std::endl; for (const auto& item : json) { values.emplace_back(item); } + + const auto tableName = std::string("Upgrade"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/upgrade-table"; + if (validationTable != nullptr) { + validationTable->add(Validation( + tableName, + "UpgradeCode", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryGuid, + "", + DescriptionWithUrl("The UpgradeCode property in this column " + "specifies the upgrade code of all products that are to be " + "detected by the FindRelatedProducts action.", url) + )); + validationTable->add(Validation( + tableName, + "VersionMin", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("Lower boundary of the range of product " + "versions detected by FindRelatedProducts.", url) + )); + validationTable->add(Validation( + tableName, + "VersionMax", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("Upper boundary of the range of product " + "versions detected by the FindRelatedProducts action.", url) + )); + validationTable->add(Validation( + tableName, + "Language", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("The set of languages detected by " + "FindRelatedProducts.", url) + )); + validationTable->add(Validation( + tableName, + "Attributes", + false, + 0, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("This column contains bit flags specifying " + "attributes of the Upgrade table.", url) + )); + validationTable->add(Validation( + tableName, + "Remove", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryFormatted, + "", + DescriptionWithUrl("The installer sets the REMOVE property to " + "features specified in this column.", url) + )); + validationTable->add(Validation( + tableName, + "ActionProperty", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("When the FindRelatedProducts action detects a " + "related product installed on the system, it appends the " + "product code to the property specified in this field.", url) + )); + } } bool UpgradeTable::generate(MSIHANDLE hDatabase) { diff --git a/installer/UpgradeTable.h b/installer/UpgradeTable.h index e276bca03b8..5d64df7d9c4 100644 --- a/installer/UpgradeTable.h +++ b/installer/UpgradeTable.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // https://boinc.berkeley.edu -// Copyright (C) 2024 University of California +// Copyright (C) 2025 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -19,10 +19,12 @@ #include "Upgrade.h" #include "Generator.h" +#include "ValidationTable.h" class UpgradeTable : public Generator { public: - explicit UpgradeTable(const nlohmann::json& json); + explicit UpgradeTable(const nlohmann::json& json, + std::shared_ptr validationTable); ~UpgradeTable() = default; bool generate(MSIHANDLE hDatabase) override; private: diff --git a/installer/Validation.cpp b/installer/Validation.cpp new file mode 100644 index 00000000000..040b203d788 --- /dev/null +++ b/installer/Validation.cpp @@ -0,0 +1,35 @@ +// This file is part of BOINC. +// https://boinc.berkeley.edu +// Copyright (C) 2025 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +#include "Validation.h" +#include "MsiHelper.h" + +Validation::Validation(const std::string& table, const std::string& column, + bool nullable, int minValue, int maxValue, const std::string& keyTable, + int keyColumn, const std::string& category, const std::string& set, + const std::string& description) : table(table), column(column), + nullable(nullable), minValue(minValue), maxValue(maxValue), + keyTable(keyTable), keyColumn(keyColumn), category(category), set(set), + description(description) { +} + +MSIHANDLE Validation::getRecord() const { + const std::string yval = "Y"; + const std::string nval = "N"; + return MsiHelper::MsiRecordSet({ table, column, nullable ? yval : nval, + minValue, maxValue, keyTable, keyColumn, category, set, description }); +} diff --git a/installer/Validation.h b/installer/Validation.h new file mode 100644 index 00000000000..8c7dffd9c85 --- /dev/null +++ b/installer/Validation.h @@ -0,0 +1,41 @@ +// This file is part of BOINC. +// https://boinc.berkeley.edu +// Copyright (C) 2025 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +#pragma once + +#include "Record.h" + +class Validation : public Record { +public: + explicit Validation(const std::string& table, const std::string& column, + bool nullable, int minValue, int maxValue, const std::string& keyTable, + int keyColumn, const std::string& category, const std::string& set, + const std::string& description); + ~Validation() = default; + MSIHANDLE getRecord() const override; +private: + std::string table{}; + std::string column{}; + bool nullable = false; + int minValue = MSI_NULL_INTEGER; + int maxValue = MSI_NULL_INTEGER; + std::string keyTable{}; + int keyColumn = MSI_NULL_INTEGER; + std::string category{}; + std::string set{}; + std::string description{}; +}; diff --git a/installer/ValidationTable.cpp b/installer/ValidationTable.cpp new file mode 100644 index 00000000000..b7cf45b1749 --- /dev/null +++ b/installer/ValidationTable.cpp @@ -0,0 +1,168 @@ +// This file is part of BOINC. +// https://boinc.berkeley.edu +// Copyright (C) 2025 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +#include "ValidationTable.h" + +ValidationTable::ValidationTable() { + const auto tableName = std::string("_Validation"); + const auto url = "https://learn.microsoft.com/en-us/windows/win32/msi/-validation-table"; + values.emplace_back( + tableName, + "Table", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Used to identify a particular table.", url) + ); + values.emplace_back( + tableName, + "Column", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("Used to identify a particular column of the table", + url) + ); + values.emplace_back( + tableName, + "Nullable", + false, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "Y;N", + DescriptionWithUrl("Identifies whether the column may contain a Null " + "value.", url) + ); + values.emplace_back(tableName, + "MinValue", + true, + -2147483647, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The field contains the minimum permissible value.", + url) + ); + values.emplace_back(tableName, + "MaxValue", + true, + -2147483647, + 2147483647, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("The field is the maximum permissible value.", url) + ); + values.emplace_back(tableName, + "KeyTable", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryIdentifier, + "", + DescriptionWithUrl("The table name with the foreign keys", url) + ); + values.emplace_back(tableName, + "KeyColumn", + true, + 1, + 32, + "", + MSI_NULL_INTEGER, + "", + "", + DescriptionWithUrl("This field applies to table columns that are " + "external keys", url) + ); + values.emplace_back(tableName, + "Category", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + "", + "Text;Formatted;Template;Condition;Guid;Path;Version;Language;" + "Identifier;Binary;UpperCase;LowerCase;Filename;Paths;AnyPath;" + "WildCardFilename;RegPath;KeyFormatted;CustomSource;Property;Cabinet;" + "Shortcut;URL", + DescriptionWithUrl("This is the type of data contained by the " + "database field specified by the Table and Column columns of the " + "_Validation table.", url) + ); + values.emplace_back(tableName, + "Set", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("This is a list of permissible values for this " + "field separated by semicolons.", url) + ); + values.emplace_back(tableName, + "Description", + true, + MSI_NULL_INTEGER, + MSI_NULL_INTEGER, + "", + MSI_NULL_INTEGER, + ValidationCategoryText, + "", + DescriptionWithUrl("A description of the data that is stored in the " + "column.", url) + ); +} + +bool ValidationTable::generate(MSIHANDLE hDatabase) { + std::cout << "Generating ValidationTable..." << std::endl; + + const auto sql_create = "CREATE TABLE `_Validation` " + "(`Table` CHAR(32) NOT NULL, `Column` CHAR(32) NOT NULL, " + "`Nullable` CHAR(4) NOT NULL, `MinValue` LONG, `MaxValue` LONG, " + "`KeyTable` CHAR(255), `KeyColumn` SHORT, `Category` CHAR(32), " + "`Set` CHAR(255), `Description` CHAR(255) " + "PRIMARY KEY `Table`, `Column`)"; + const auto sql_insert = "INSERT INTO `_Validation` " + "(`Table`, `Column`, `Nullable`, `MinValue`, `MaxValue`, `KeyTable`, " + "`KeyColumn`, `Category`, `Set`, `Description`) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + return Generator::generate(hDatabase, sql_create, sql_insert, values); +} + +void ValidationTable::add(const Validation& value) { + values.emplace_back(value); +} diff --git a/installer/ValidationTable.h b/installer/ValidationTable.h new file mode 100644 index 00000000000..fff801ee245 --- /dev/null +++ b/installer/ValidationTable.h @@ -0,0 +1,56 @@ +// This file is part of BOINC. +// https://boinc.berkeley.edu +// Copyright (C) 2025 University of California +// +// BOINC is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// BOINC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with BOINC. If not, see . + +#pragma once + +#include "Generator.h" +#include "Validation.h" + +const auto ValidationCategoryIdentifier = std::string("Identifier"); +const auto ValidationCategoryText = std::string("Text"); +const auto ValidationCategoryTemplate = std::string("Template"); +const auto ValidationCategoryCondition = std::string("Condition"); +const auto ValidationCategoryBinary = std::string("Binary"); +const auto ValidationCategoryFormatted = std::string("Formatted"); +const auto ValidationCategoryGuid = std::string("Guid"); +const auto ValidationCategoryCustomSource = std::string("CustomSource"); +const auto ValidationCategoryDefaultDir = std::string("DefaultDir"); +const auto ValidationCategoryUpperCase = std::string("UpperCase"); +const auto ValidationCategoryFilename = std::string("Filename"); +const auto ValidationCategoryVersion = std::string("Version"); +const auto ValidationCategoryLanguage = std::string("Language"); +const auto ValidationCategoryCabinet = std::string("Cabinet"); +const auto ValidationCategoryProperty = std::string("Property"); +const auto ValidationCategoryRegPath = std::string("RegPath"); +const auto ValidationCategoryWildCardFilename = +std::string("WildCardFilename"); +const auto ValidationCategoryShortcut = std::string("Shortcut"); + +const auto DescriptionWithUrl = [](const std::string& description, + const std::string& url) { + return description + " For more information, see " + url; + }; + +class ValidationTable : public Generator { +public: + explicit ValidationTable(); + ~ValidationTable() = default; + bool generate(MSIHANDLE hDatabase) override; + void add(const Validation& value); +private: + std::vector values{}; +}; diff --git a/installer/boinc.json b/installer/boinc.json index 894e6976c57..e8c80e2b35e 100644 --- a/installer/boinc.json +++ b/installer/boinc.json @@ -8,7 +8,7 @@ "comments": "IDS_FEATURE_BOINC_DESCRIPTION", "template": "%%PLATFORM%%;1033", "lastauthor": "BOINC", - "pagecount": "200:500", + "pagecount": "400:500", "wordcount": 0, "charcount": 0, "appname": "BOINC", @@ -1969,8 +1969,8 @@ "Text": "IDS_BACK", "Events": [ { - "Event": "NewDialog", - "Argument": "NoDialog", + "Event": "EndDialog", + "Argument": "Return", "Condition": "1", "Ordering": 1 } @@ -2599,7 +2599,7 @@ "Y": 75, "Width": 24, "Height": 24, - "Attributes": 5767169, + "Attributes": 6291457, "Binary_": "reinstall.ico" }, { @@ -2609,7 +2609,7 @@ "Y": 135, "Width": 24, "Height": 24, - "Attributes": 5767169, + "Attributes": 6291457, "Binary_": "remove.ico" }, { @@ -3084,8 +3084,8 @@ "Ordering": 0 }, { - "Event": "NewDialog", - "Argument": "NoDialog", + "Event": "EndDialog", + "Argument": "Return", "Condition": "ACTION <> \"ADMIN\"", "Ordering": 0 } @@ -4213,7 +4213,7 @@ "Y": 52, "Width": 24, "Height": 24, - "Attributes": 5767169, + "Attributes": 6291457, "Binary_": "folder.ico" }, { @@ -4223,7 +4223,7 @@ "Y": 87, "Width": 24, "Height": 24, - "Attributes": 5767169, + "Attributes": 6291457, "Binary_": "folder.ico" }, { @@ -4514,7 +4514,7 @@ "Y": 52, "Width": 24, "Height": 24, - "Attributes": 5767169, + "Attributes": 6291457, "Binary_": "folder.ico" }, { @@ -4524,7 +4524,7 @@ "Y": 87, "Width": 24, "Height": 24, - "Attributes": 5767169, + "Attributes": 6291457, "Binary_": "folder.ico" }, { @@ -5578,13 +5578,7 @@ "Feature_": "BOINC", "Component": "_BOINCDataProjects", "Attributes": 272, - "Files": [ - { - "File": "placeholder.txt1", - "FileName": "PLACEH~1.TXT|placeholder.txt", - "FilePath": "res\\placeholder.txt" - } - ] + "CreateFolder": true } ] }, @@ -5596,13 +5590,7 @@ "Feature_": "BOINC", "Component": "_BOINCDataSlots", "Attributes": 272, - "Files": [ - { - "File": "placeholder.txt2", - "FileName": "PLACEH~1.TXT|placeholder.txt", - "FilePath": "res\\placeholder.txt" - } - ] + "CreateFolder": true } ] } @@ -5612,16 +5600,10 @@ "Feature_": "BOINC", "Component": "_BOINCData", "Attributes": 272, + "CreateFolder": true, "Files": [ { - "File": "all_projects_list.xml", - "FileName": "ALL_PR~1.XML|all_projects_list.xml", "FilePath": "..\\win_build\\installerv2\\redist\\all_projects_list.xml" - }, - { - "File": "placeholder.txt", - "FileName": "PLACEH~1.TXT|placeholder.txt", - "FilePath": "res\\placeholder.txt" } ] } @@ -5651,28 +5633,18 @@ "Attributes": 256, "Files": [ { - "File": "DEFAULT_skin.xml", - "FileName": "skin.xml", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\skins\\\\Default\\skin.xml" }, { - "File": "DEFAULT_background_image.png", - "FileName": "background_image.png", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\skins\\\\Default\\background_image.png" }, { - "File": "DEFAULT_workunit_running_image.png", - "FileName": "workunit_running_image.png", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\skins\\\\Default\\workunit_running_image.png" }, { - "File": "DEFAULT_workunit_suspended_image.png", - "FileName": "workunit_suspended_image.png", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\skins\\\\Default\\workunit_suspended_image.png" }, { - "File": "DEFAULT_workunit_waiting_image.png", - "FileName": "workunit_waiting_image.png", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\skins\\\\Default\\workunit_waiting_image.png" } ] @@ -5686,7 +5658,7 @@ "DefaultDir": "locale", "Directories": [ { - "Directory": "ar", + "Directory": "locale_ar", "DefaultDir": "ar", "Components": [ { @@ -5694,8 +5666,6 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_ar_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ar\\BOINC-Client.mo" } ] @@ -5703,7 +5673,7 @@ ] }, { - "Directory": "az", + "Directory": "locale_az", "DefaultDir": "az", "Components": [ { @@ -5711,13 +5681,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_az_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\az\\BOINC-Client.mo" }, { - "File": "LOCALE_az_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\az\\BOINC-Manager.mo" } ] @@ -5725,7 +5691,7 @@ ] }, { - "Directory": "bg", + "Directory": "locale_bg", "DefaultDir": "bg", "Components": [ { @@ -5733,13 +5699,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_bg_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\bg\\BOINC-Client.mo" }, { - "File": "LOCALE_bg_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\bg\\BOINC-Manager.mo" } ] @@ -5747,7 +5709,7 @@ ] }, { - "Directory": "ca", + "Directory": "locale_ca", "DefaultDir": "ca", "Components": [ { @@ -5755,13 +5717,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_ca_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ca\\BOINC-Client.mo" }, { - "File": "LOCALE_ca_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ca\\BOINC-Manager.mo" } ] @@ -5769,7 +5727,7 @@ ] }, { - "Directory": "cs", + "Directory": "locale_cs", "DefaultDir": "cs", "Components": [ { @@ -5777,13 +5735,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_cs_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\cs\\BOINC-Client.mo" }, { - "File": "LOCALE_cs_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\cs\\BOINC-Manager.mo" } ] @@ -5791,7 +5745,7 @@ ] }, { - "Directory": "da", + "Directory": "locale_da", "DefaultDir": "da", "Components": [ { @@ -5799,13 +5753,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_da_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\da\\BOINC-Client.mo" }, { - "File": "LOCALE_da_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\da\\BOINC-Manager.mo" } ] @@ -5813,7 +5763,7 @@ ] }, { - "Directory": "de", + "Directory": "locale_de", "DefaultDir": "de", "Components": [ { @@ -5821,13 +5771,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_de_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\de\\BOINC-Client.mo" }, { - "File": "LOCALE_de_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\de\\BOINC-Manager.mo" } ] @@ -5835,7 +5781,7 @@ ] }, { - "Directory": "el", + "Directory": "locale_el", "DefaultDir": "el", "Components": [ { @@ -5843,8 +5789,6 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_el_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\el\\BOINC-Client.mo" } ] @@ -5852,7 +5796,7 @@ ] }, { - "Directory": "eo", + "Directory": "locale_eo", "DefaultDir": "eo", "Components": [ { @@ -5860,8 +5804,6 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_eo_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\eo\\BOINC-Client.mo" } ] @@ -5869,7 +5811,7 @@ ] }, { - "Directory": "es", + "Directory": "locale_es", "DefaultDir": "es", "Components": [ { @@ -5877,13 +5819,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_es_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\es\\BOINC-Client.mo" }, { - "File": "LOCALE_es_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\es\\BOINC-Manager.mo" } ] @@ -5891,7 +5829,7 @@ ] }, { - "Directory": "eu", + "Directory": "locale_eu", "DefaultDir": "eu", "Components": [ { @@ -5899,13 +5837,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_eu_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\eu\\BOINC-Client.mo" }, { - "File": "LOCALE_eu_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\eu\\BOINC-Manager.mo" } ] @@ -5913,7 +5847,7 @@ ] }, { - "Directory": "fa_IR", + "Directory": "locale_fa_IR", "DefaultDir": "fa_IR", "Components": [ { @@ -5921,8 +5855,6 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_fa_IR_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\fa_IR\\BOINC-Client.mo" } ] @@ -5930,7 +5862,7 @@ ] }, { - "Directory": "fi", + "Directory": "locale_fi", "DefaultDir": "fi", "Components": [ { @@ -5938,13 +5870,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_fi_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\fi\\BOINC-Client.mo" }, { - "File": "LOCALE_fi_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\fi\\BOINC-Manager.mo" } ] @@ -5952,7 +5880,7 @@ ] }, { - "Directory": "fr", + "Directory": "locale_fr", "DefaultDir": "fr", "Components": [ { @@ -5960,13 +5888,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_fr_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\fr\\BOINC-Client.mo" }, { - "File": "LOCALE_fr_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\fr\\BOINC-Manager.mo" } ] @@ -5974,7 +5898,7 @@ ] }, { - "Directory": "he", + "Directory": "locale_he", "DefaultDir": "he", "Components": [ { @@ -5982,13 +5906,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_hr_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\he\\BOINC-Client.mo" }, { - "File": "LOCALE_he_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\he\\BOINC-Manager.mo" } ] @@ -5996,7 +5916,7 @@ ] }, { - "Directory": "hu", + "Directory": "locale_hu", "DefaultDir": "hu", "Components": [ { @@ -6004,13 +5924,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_hu_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\hu\\BOINC-Client.mo" }, { - "File": "LOCALE_hu_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\hu\\BOINC-Manager.mo" } ] @@ -6018,7 +5934,7 @@ ] }, { - "Directory": "it", + "Directory": "locale_it", "DefaultDir": "it", "Components": [ { @@ -6026,13 +5942,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_it_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\it\\BOINC-Client.mo" }, { - "File": "LOCALE_it_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\it\\BOINC-Manager.mo" } ] @@ -6040,7 +5952,7 @@ ] }, { - "Directory": "it_IT", + "Directory": "locale_it_IT", "DefaultDir": "it_IT", "Components": [ { @@ -6048,13 +5960,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_it_IT_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\it_IT\\BOINC-Client.mo" }, { - "File": "LOCALE_it_IT_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\it_IT\\BOINC-Manager.mo" } ] @@ -6062,7 +5970,7 @@ ] }, { - "Directory": "ja", + "Directory": "locale_ja", "DefaultDir": "ja", "Components": [ { @@ -6070,13 +5978,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_ja_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ja\\BOINC-Client.mo" }, { - "File": "LOCALE_ja_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ja\\BOINC-Manager.mo" } ] @@ -6084,7 +5988,7 @@ ] }, { - "Directory": "ka", + "Directory": "locale_ka", "DefaultDir": "ka", "Components": [ { @@ -6092,13 +5996,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_ka_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ka\\BOINC-Client.mo" }, { - "File": "LOCALE_ka_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ka\\BOINC-Manager.mo" } ] @@ -6106,7 +6006,7 @@ ] }, { - "Directory": "ko", + "Directory": "locale_ko", "DefaultDir": "ko", "Components": [ { @@ -6114,13 +6014,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_ko_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ko\\BOINC-Client.mo" }, { - "File": "LOCALE_ko_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ko\\BOINC-Manager.mo" } ] @@ -6128,7 +6024,7 @@ ] }, { - "Directory": "lt", + "Directory": "locale_lt", "DefaultDir": "lt", "Components": [ { @@ -6136,8 +6032,6 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_lt_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\lt\\BOINC-Client.mo" } ] @@ -6145,7 +6039,7 @@ ] }, { - "Directory": "lv", + "Directory": "locale_lv", "DefaultDir": "lv", "Components": [ { @@ -6153,13 +6047,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_lv_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\lv\\BOINC-Client.mo" }, { - "File": "LOCALE_lv_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\lv\\BOINC-Manager.mo" } ] @@ -6167,7 +6057,7 @@ ] }, { - "Directory": "nb", + "Directory": "locale_nb", "DefaultDir": "nb", "Components": [ { @@ -6175,13 +6065,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_nb_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\nb\\BOINC-Client.mo" }, { - "File": "LOCALE_nb_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\nb\\BOINC-Manager.mo" } ] @@ -6189,7 +6075,7 @@ ] }, { - "Directory": "nl", + "Directory": "locale_nl", "DefaultDir": "nl", "Components": [ { @@ -6197,13 +6083,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_nl_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\nl\\BOINC-Client.mo" }, { - "File": "LOCALE_nl_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\nl\\BOINC-Manager.mo" } ] @@ -6211,7 +6093,7 @@ ] }, { - "Directory": "pl", + "Directory": "locale_pl", "DefaultDir": "pl", "Components": [ { @@ -6219,13 +6101,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_pl_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\pl\\BOINC-Client.mo" }, { - "File": "LOCALE_pl_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\pl\\BOINC-Manager.mo" } ] @@ -6233,7 +6111,7 @@ ] }, { - "Directory": "pt_BR", + "Directory": "locale_pt_BR", "DefaultDir": "pt_BR", "Components": [ { @@ -6241,13 +6119,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_pt_BR_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\pt_BR\\BOINC-Client.mo" }, { - "File": "LOCALE_pt_BR_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\pt_BR\\BOINC-Manager.mo" } ] @@ -6255,7 +6129,7 @@ ] }, { - "Directory": "pt_PT", + "Directory": "locale_pt_PT", "DefaultDir": "pt_PT", "Components": [ { @@ -6263,13 +6137,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_pt_PT_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\pt_PT\\BOINC-Client.mo" }, { - "File": "LOCALE_pt_PT_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\pt_PT\\BOINC-Manager.mo" } ] @@ -6277,7 +6147,7 @@ ] }, { - "Directory": "ro", + "Directory": "locale_ro", "DefaultDir": "ro", "Components": [ { @@ -6285,13 +6155,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_ro_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ro\\BOINC-Client.mo" }, { - "File": "LOCALE_ro_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ro\\BOINC-Manager.mo" } ] @@ -6299,7 +6165,7 @@ ] }, { - "Directory": "ru", + "Directory": "locale_ru", "DefaultDir": "ru", "Components": [ { @@ -6307,13 +6173,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_ru_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ru\\BOINC-Client.mo" }, { - "File": "LOCALE_ru_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\ru\\BOINC-Manager.mo" } ] @@ -6321,7 +6183,7 @@ ] }, { - "Directory": "sk", + "Directory": "locale_sk", "DefaultDir": "sk", "Components": [ { @@ -6329,13 +6191,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_sk_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\sk\\BOINC-Client.mo" }, { - "File": "LOCALE_sk_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\sk\\BOINC-Manager.mo" } ] @@ -6343,7 +6201,7 @@ ] }, { - "Directory": "sr@latin", + "Directory": "locale_sr_latin", "DefaultDir": "sr@latin", "Components": [ { @@ -6351,8 +6209,6 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_sr_latin_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\sr@latin\\BOINC-Client.mo" } ] @@ -6360,7 +6216,7 @@ ] }, { - "Directory": "sv", + "Directory": "locale_sv", "DefaultDir": "sv", "Components": [ { @@ -6368,13 +6224,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_sv_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\sv\\BOINC-Client.mo" }, { - "File": "LOCALE_sv_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\sv\\BOINC-Manager.mo" } ] @@ -6382,7 +6234,7 @@ ] }, { - "Directory": "tr", + "Directory": "locale_tr", "DefaultDir": "tr", "Components": [ { @@ -6390,13 +6242,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_tr_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\tr\\BOINC-Client.mo" }, { - "File": "LOCALE_tr_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\tr\\BOINC-Manager.mo" } ] @@ -6404,7 +6252,7 @@ ] }, { - "Directory": "uk", + "Directory": "locale_uk", "DefaultDir": "uk", "Components": [ { @@ -6412,13 +6260,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_uk_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\uk\\BOINC-Client.mo" }, { - "File": "LOCALE_uk_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\uk\\BOINC-Manager.mo" } ] @@ -6426,7 +6270,7 @@ ] }, { - "Directory": "vi", + "Directory": "locale_vi", "DefaultDir": "vi", "Components": [ { @@ -6434,13 +6278,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_vi_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\vi\\BOINC-Client.mo" }, { - "File": "LOCALE_vi_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\vi\\BOINC-Manager.mo" } ] @@ -6448,7 +6288,7 @@ ] }, { - "Directory": "zh_CN", + "Directory": "locale_zh_CN", "DefaultDir": "zh_CN", "Components": [ { @@ -6456,13 +6296,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_zh_CN_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\zh_CN\\BOINC-Client.mo" }, { - "File": "LOCALE_zh_CN_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\zh_CN\\BOINC-Manager.mo" } ] @@ -6470,7 +6306,7 @@ ] }, { - "Directory": "zh_TW", + "Directory": "locale_zh_TW", "DefaultDir": "zh_TW", "Components": [ { @@ -6478,13 +6314,9 @@ "Attributes": 256, "Files": [ { - "File": "LOCALE_zh_TW_BOINC-Client.mo", - "FileName": "BOINC-Client.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\zh_TW\\BOINC-Client.mo" }, { - "File": "LOCALE_zh_TW_BOINC-Manager.mo", - "FileName": "BOINC-Manager.mo", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\locale\\zh_TW\\BOINC-Manager.mo" } ] @@ -6509,13 +6341,6 @@ "Attributes": 256, "Condition": "ENABLEPROTECTEDAPPLICATIONEXECUTION3 = 1", "CreateFolder": true, - "Files": [ - { - "File": "boinc.exe1", - "FileName": "boinc.exe", - "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\boinc.exe" - } - ], "ServiceControl": [ { "ServiceControl": "BOINC", @@ -6546,8 +6371,6 @@ "CreateFolder": true, "Files": [ { - "File": "boinc.exe", - "FileName": "boinc.exe", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\boinc.exe" } ] @@ -6558,8 +6381,6 @@ "Attributes": 256, "Files": [ { - "File": "boinccmd.exe", - "FileName": "boinccmd.exe", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\boinccmd.exe" } ] @@ -6570,13 +6391,9 @@ "Attributes": 256, "Files": [ { - "File": "copying", - "FileName": "COPYING", "FilePath": "..\\COPYING" }, { - "File": "copyright", - "FileName": "COPYRI~1|COPYRIGHT", "FilePath": "..\\COPYRIGHT" } ] @@ -6587,8 +6404,6 @@ "Attributes": 256, "Files": [ { - "File": "boincmgr.exe", - "FileName": "boincmgr.exe", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\boincmgr.exe" } ], @@ -6670,18 +6485,12 @@ "Attributes": 264, "Files": [ { - "File": "boinc_logo_black.jpg", - "FileName": "BOINC_~1.JPG|boinc_logo_black.jpg", "FilePath": "..\\doc\\logo\\boinc_logo_black.jpg" }, { - "File": "boincscr.exe", - "FileName": "boincscr.exe", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\boincscr.exe" }, { - "File": "liberationsans_regular.ttf", - "FileName": "LIBERA~1.TTF|LiberationSans-Regular.ttf", "FilePath": "..\\api\\ttf\\liberation-fonts-ttf-2.00.0\\LiberationSans-Regular.ttf", "IsFont": true } @@ -6691,11 +6500,8 @@ "Feature_": "BOINC", "Component": "_BOINCSvcCtrl", "Attributes": 256, - "CreateFolder": true, "Files": [ { - "File": "boincsvcctrl.exe", - "FileName": "BOINCS~1.EXE|boincsvcctrl.exe", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\boincsvcctrl.exe" } ] @@ -6706,8 +6512,6 @@ "Attributes": 256, "Files": [ { - "File": "boinctray.exe", - "FileName": "BOINCT~1.EXE|boinctray.exe", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\boinctray.exe" } ], @@ -6797,8 +6601,6 @@ "Attributes": 256, "Files": [ { - "File": "boinc.scr", - "FileName": "boinc.scr", "FilePath": "..\\win_build\\Build\\%%PLATFORM%%\\%%CONFIGURATION%%\\boinc.scr" } ] @@ -9934,10 +9736,12 @@ "Description": "ID_STRING39" }, { - "Condition": "AdminUser", + "Condition": "Privileged", "Description": "IDPROP_EXPRESS_LAUNCH_CONDITION_ADMIN" } ], + "Listbox": [ + ], "Property": [ { "Property": "ARPCOMMENTS", @@ -10113,7 +9917,7 @@ }, { "Property": "_IsMaintenance", - "Value": "Install" + "Value": "Reinstall" } ], "TextStyle": [ @@ -10339,24 +10143,24 @@ "Upgrade": [ { "UpgradeCode": "{76DD37FC-EE51-408D-9FB5-3D59FC8ED22A}", - "VersionMin": "0000.0000.0000", - "VersionMax": "9999.9999.9999", + "VersionMin": "0.0.0", + "VersionMax": "255.255.65535", "Attributes": 769, "ActionProperty": "ISACTIONPROP2", "Comments": "World Community Grid Upgrade (7.14.2)" }, { "UpgradeCode": "{862B80F6-835D-4F72-8C4F-EE68ED34C6F8}", - "VersionMin": "0000.0000.0000", - "VersionMax": "9999.9999.9999", + "VersionMin": "0.0.0", + "VersionMax": "255.255.65535", "Attributes": 769, "ActionProperty": "ISACTIONPROP1", "Comments": "World Community Grid Upgrade" }, { "UpgradeCode": "{E913E54D-5080-42EC-A312-B21948BA1C02}", - "VersionMin": "0000.0000.0000", - "VersionMax": "9999.9999.9999", + "VersionMin": "0.0.0", + "VersionMax": "255.255.65535", "Attributes": 769, "ActionProperty": "ACTPROP_E913E54D_5080_42EC_A312_B21948BA1C02", "Comments": "General Upgrade" diff --git a/installer/darice.cub b/installer/darice.cub new file mode 100644 index 00000000000..ea0ff829091 Binary files /dev/null and b/installer/darice.cub differ diff --git a/installer/res/placeholder.txt b/installer/res/placeholder.txt deleted file mode 100644 index dd97f12778e..00000000000 --- a/installer/res/placeholder.txt +++ /dev/null @@ -1 +0,0 @@ -This file is used as a placeholder until real data files are created or copied from another location. \ No newline at end of file diff --git a/tests/msi_validation.py b/tests/msi_validation.py new file mode 100644 index 00000000000..8dc3738b545 --- /dev/null +++ b/tests/msi_validation.py @@ -0,0 +1,66 @@ +# This file is part of BOINC. +# https://boinc.berkeley.edu +# Copyright (C) 2025 University of California +# +# BOINC is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. +# +# BOINC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with BOINC. If not, see . + +import os +import sys + +def main(): + if len(sys.argv) != 4: + print("Usage: msi_validation.py ") + sys.exit(1) + + msival2_path = sys.argv[1] + if not os.path.exists(msival2_path): + print(f"'{msival2_path}' not found") + sys.exit(1) + + msi_path = sys.argv[2] + if not os.path.exists(msi_path): + print(f"'{msi_path}' not found") + sys.exit(1) + + cub_path = sys.argv[3] + if not os.path.exists(cub_path): + print("'{cub_path}' not found") + sys.exit(1) + + ignore_list = [ + "ICE Type Description", + "ICE07 WARNING '_BOINCScreensaver_LiberationSans_Regular.ttf' is a Font and must be installed to the FontsFolder. Current Install Directory: 'INSTALLDIR'", + "ICE43 ERROR Component _BOINCManagerStartMenu has non-advertised shortcuts. It should use a registry key under HKCU as its KeyPath, not a file.", + "ICE57 ERROR Component '_ScreensaverEnableNT' has both per-user and per-machine data with a per-machine KeyPath.", + "ICE57 ERROR Component '_BOINCManagerStartup' has both per-user and per-machine data with a per-machine KeyPath.", + "ICE57 ERROR Component '_BOINCManagerStartMenu' has both per-user and per-machine data with a per-machine KeyPath.", + "ICE61 WARNING This product should remove only older versions of itself. The Maximum version is not less than the current product.", + ] + output = os.popen(f'"{msival2_path}" "{msi_path}" "{cub_path}" -f').read() + error_found = False + for line in output.splitlines(): + if line == '' or any(ignore in line for ignore in ignore_list): + continue + error_found = True + print(line) + + if error_found: + print("Validation failed") + sys.exit(1) + + print("Validation succeeded") + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/win_build/installer.vcxproj b/win_build/installer.vcxproj index 831937b390b..46a8abd8719 100644 --- a/win_build/installer.vcxproj +++ b/win_build/installer.vcxproj @@ -13,6 +13,7 @@ .;..;../api;../lib;$(VcpkgRootDir)\installed\arm64-windows-static\include\;%(AdditionalIncludeDirectories) _CONSOLE;%(PreprocessorDefinitions) + MSI_VALIDATE;%(PreprocessorDefinitions) stdcpp17 @@ -67,6 +68,8 @@ + + @@ -99,6 +102,8 @@ + + @@ -148,6 +153,8 @@ + + @@ -181,8 +188,10 @@ + + - + diff --git a/win_build/installer_msi.vcxproj b/win_build/installer_msi.vcxproj index e59b9d438a7..ad8ed80ac23 100644 --- a/win_build/installer_msi.vcxproj +++ b/win_build/installer_msi.vcxproj @@ -25,6 +25,7 @@ + $(SolutionDir)..\installer\boinc.json;$(SolutionDir)..\installer\locale\en.json;$(SolutionDir)Build\x64\$(Configuration)\installer.exe;$(SolutionDir)Build\$(Platform)\$(Configuration)\boinccas.dll $(SolutionDir)Build\x64\$(Configuration)\installer.exe -p $(Platform) $(OutDir)$(TargetName)$(TargetExt)