diff --git a/.gitignore b/.gitignore index a8f98d4cd..cdb40cc47 100644 --- a/.gitignore +++ b/.gitignore @@ -406,3 +406,5 @@ src/msix/common/MSIXResource.cpp src/test/MacOS-Linux/testApiResults.txt src/test/mobile/iOSBVT/iOSBVT.xcodeproj/project.xcworkspace/ src/test/mobile/TEST-MsixSDK-AOSP.xml + +Thumbs.db diff --git a/CODEOWNERS b/CODEOWNERS index 4f72319d8..9ae313adb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,3 +3,9 @@ # MsixCoreInstaller owners /MsixCore/ @wcheng-msft @jyvenugo @sarjanas-msft + +# Utils library +/tools/utils/ @ranm-msft @msftrubengu @johnmcpms @lechacon @jamespik + +# Azure Pipelines tasks +/tools/pipelines-tasks @ranm-msft @msftrubengu @johnmcpms @lechacon \ No newline at end of file diff --git a/MsixCore/Dependencies/x64/ApplyACLs.dll b/MsixCore/Dependencies/x64/ApplyACLs.dll index 69557ae21..b8cbb2f18 100644 Binary files a/MsixCore/Dependencies/x64/ApplyACLs.dll and b/MsixCore/Dependencies/x64/ApplyACLs.dll differ diff --git a/MsixCore/Dependencies/x64/ApplyACLs.pdb b/MsixCore/Dependencies/x64/ApplyACLs.pdb index 40716858a..21307b6d8 100644 Binary files a/MsixCore/Dependencies/x64/ApplyACLs.pdb and b/MsixCore/Dependencies/x64/ApplyACLs.pdb differ diff --git a/MsixCore/Dependencies/x64/CreateCIM.dll b/MsixCore/Dependencies/x64/CreateCIM.dll new file mode 100644 index 000000000..b252bb78f Binary files /dev/null and b/MsixCore/Dependencies/x64/CreateCIM.dll differ diff --git a/MsixCore/Dependencies/x64/CreateCIM.pdb b/MsixCore/Dependencies/x64/CreateCIM.pdb new file mode 100644 index 000000000..217267547 Binary files /dev/null and b/MsixCore/Dependencies/x64/CreateCIM.pdb differ diff --git a/MsixCore/Dependencies/x64/WVDUtilities.dll b/MsixCore/Dependencies/x64/WVDUtilities.dll new file mode 100644 index 000000000..ae41eecb8 Binary files /dev/null and b/MsixCore/Dependencies/x64/WVDUtilities.dll differ diff --git a/MsixCore/Dependencies/x64/WVDUtilities.pdb b/MsixCore/Dependencies/x64/WVDUtilities.pdb new file mode 100644 index 000000000..d9c9aa5c9 Binary files /dev/null and b/MsixCore/Dependencies/x64/WVDUtilities.pdb differ diff --git a/MsixCore/Dependencies/x86/ApplyACLs.dll b/MsixCore/Dependencies/x86/ApplyACLs.dll index 6f9de8254..6f0cbf517 100644 Binary files a/MsixCore/Dependencies/x86/ApplyACLs.dll and b/MsixCore/Dependencies/x86/ApplyACLs.dll differ diff --git a/MsixCore/Dependencies/x86/ApplyACLs.pdb b/MsixCore/Dependencies/x86/ApplyACLs.pdb index 18baa7f0f..c88089eb9 100644 Binary files a/MsixCore/Dependencies/x86/ApplyACLs.pdb and b/MsixCore/Dependencies/x86/ApplyACLs.pdb differ diff --git a/MsixCore/Dependencies/x86/CreateCIM.dll b/MsixCore/Dependencies/x86/CreateCIM.dll new file mode 100644 index 000000000..ea7469b60 Binary files /dev/null and b/MsixCore/Dependencies/x86/CreateCIM.dll differ diff --git a/MsixCore/Dependencies/x86/CreateCIM.pdb b/MsixCore/Dependencies/x86/CreateCIM.pdb new file mode 100644 index 000000000..f9267cc01 Binary files /dev/null and b/MsixCore/Dependencies/x86/CreateCIM.pdb differ diff --git a/MsixCore/msixmgr/CIMProvider.cpp b/MsixCore/msixmgr/CIMProvider.cpp new file mode 100644 index 000000000..db446e4ba --- /dev/null +++ b/MsixCore/msixmgr/CIMProvider.cpp @@ -0,0 +1,118 @@ +#include "MSIXWindows.hpp" +#include "CIMProvider.hpp" +#include "ApplyACLsProvider.hpp" +#include "msixmgrLogger.hpp" +#include "..\msixmgrLib\GeneralUtil.hpp" +#include +#include +#include +#include +#include "InstallUI.hpp" + +using namespace MsixCoreLib; +using namespace std; + +namespace MsixCoreLib +{ + class CreateCIMDll + { + public: + CreateCIMDll::CreateCIMDll() + { + + } + + CreateCIMDll::~CreateCIMDll() + { + if (module != nullptr) + { + FreeLibrary(module); + } + } + + HRESULT load() + { + module = LoadLibrary(L"createcim.dll"); + if (module == nullptr) + { + std::wcout << std::endl; + std::wcout << "Failed to load createcim.dll. Please confirm the dll is next to this exe." << std::endl; + std::wcout << std::endl; + + return HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND); + } + return S_OK; + } + + HMODULE get() + { + return module; + } + + private: + HMODULE module; + }; + + HRESULT CreateAndAddToCIM( + _In_ std::wstring cimPath, + _In_ std::wstring sourcePath, + _In_ std::wstring rootDirectory) + { + CreateCIMDll createCIM; + RETURN_IF_FAILED(createCIM.load()); + + typedef HRESULT(STDMETHODCALLTYPE *CREATEANDADDTOCIMFILE)( + std::wstring cimFilePath, + std::wstring sourceRootPath, + std::wstring imageRootPath); + + CREATEANDADDTOCIMFILE CreateAndAddToCimFileFunc = + reinterpret_cast + (GetProcAddress(createCIM.get(), "CreateAndAddToCIMFile")); + + RETURN_IF_FAILED(CreateAndAddToCimFileFunc(cimPath, sourcePath, rootDirectory)); + + return S_OK; + } + + HRESULT MountCIM( + std::wstring cimFilePath, + std::wstring& volumeId) + { + CreateCIMDll createCIM; + RETURN_IF_FAILED(createCIM.load()); + + typedef HRESULT(STDMETHODCALLTYPE *MOUNTCIM)( + std::wstring cimFilePath, + std::wstring& volumeId); + + MOUNTCIM MountCIMFunc = + reinterpret_cast + (GetProcAddress(createCIM.get(), "MountCIM")); + + RETURN_IF_FAILED(MountCIMFunc(cimFilePath, volumeId)); + + return S_OK; + } + + HRESULT UnmountCIM( + _In_opt_ std::wstring cimFilePath, + _In_opt_ std::wstring volumeIdString) + { + CreateCIMDll createCIM; + RETURN_IF_FAILED(createCIM.load()); + + typedef HRESULT(STDMETHODCALLTYPE *UNMOUNTCIM)( + std::wstring cimFilePath, + std::wstring volumeIdString); + + UNMOUNTCIM UnmountCIMFunc = + reinterpret_cast + (GetProcAddress(createCIM.get(), "UnmountCIM")); + + RETURN_IF_FAILED(UnmountCIMFunc(cimFilePath, volumeIdString)); + + return S_OK; + } + +} \ No newline at end of file diff --git a/MsixCore/msixmgr/CIMProvider.hpp b/MsixCore/msixmgr/CIMProvider.hpp new file mode 100644 index 000000000..4c300b85b --- /dev/null +++ b/MsixCore/msixmgr/CIMProvider.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace MsixCoreLib +{ + HRESULT CreateAndAddToCIM( + _In_ std::wstring cimPath, + _In_ std::wstring sourcePath, + _In_ std::wstring rootDirectory); + + HRESULT MountCIM( + _In_ std::wstring cimFilePath, + _Out_ std::wstring& volumeId); + + HRESULT UnmountCIM( + _In_opt_ std::wstring cimFilePath, + _In_opt_ std::wstring volumeIdString); +} + + + diff --git a/MsixCore/msixmgr/CommandLineInterface.cpp b/MsixCore/msixmgr/CommandLineInterface.cpp index f8cdad6f7..c4e1da56a 100644 --- a/MsixCore/msixmgr/CommandLineInterface.cpp +++ b/MsixCore/msixmgr/CommandLineInterface.cpp @@ -127,6 +127,71 @@ std::map CommandLineInterface::s_opt commandLineInterface->m_validateSignature = true; return S_OK; }), + }, + { + L"-create", + Option(false, IDS_STRING_HELP_OPTION_UNPACK_CREATE, + [&](CommandLineInterface* commandLineInterface, const std::string&) + { + if (commandLineInterface->m_operationType != OperationType::Unpack) + { + return E_INVALIDARG; + } + commandLineInterface->m_create = true; + return S_OK; + }), + }, + { + L"-rootDirectory", + Option(true, IDS_STRING_HELP_OPTION_UNPACK_ROOTDIRECTORY, + [&](CommandLineInterface* commandLineInterface, const std::string& rootDirectory) + { + if (commandLineInterface->m_operationType != OperationType::Unpack) + { + return E_INVALIDARG; + } + commandLineInterface->m_rootDirectory = utf8_to_utf16(rootDirectory); + return S_OK; + }), + }, + { + L"-fileType", + Option(true, IDS_STRING_HELP_OPTION_UNPACK_FILETYPE, + [&](CommandLineInterface* commandLineInterface, const std::string& fileType) + { + if (commandLineInterface->m_operationType != OperationType::Unpack) + { + return E_INVALIDARG; + } + commandLineInterface->SetWVDFileType(utf8_to_utf16(fileType)); + + return S_OK; + }), + }, + { + L"-vhdSize", + Option(true, IDS_STRING_HELP_OPTION_UNPACK_VHDSIZE, + [&](CommandLineInterface* commandLineInterface, const std::string& vhdSize) + { + if (commandLineInterface->m_operationType != OperationType::Unpack) + { + return E_INVALIDARG; + } + + ULONGLONG maxVhdSizeMB = 2040000ull; // MAX SIZE: 2040 GB --> 2040000 MB + errno = 0; + ULONGLONG vhdSizeUll = strtoull(vhdSize.c_str(), NULL, 10 /*base*/); + if ((vhdSizeUll == ULLONG_MAX && errno == ERANGE) || + vhdSizeUll > maxVhdSizeMB || + vhdSizeUll < 5ull) + { + std::wcout << "\nInvalid VHD size. Specified value must be at least 5 MB and at most 2040000 MB\n" << std::endl; + return E_INVALIDARG; + } + + commandLineInterface->m_vhdSize = vhdSizeUll; + return S_OK; + }), } }) }, @@ -158,6 +223,125 @@ std::map CommandLineInterface::s_opt } }) }, + { + L"-MountImage", + Options(false, IDS_STRING_HELP_OPTION_MOUNTIMAGE, + [&](CommandLineInterface* commandLineInterface, const std::string&) + { + if (commandLineInterface->m_operationType != OperationType::Undefined) + { + return E_INVALIDARG; + } + commandLineInterface->m_operationType = OperationType::MountImage; + return S_OK; + }, + { + { + L"-imagePath", + Option(true, IDS_STRING_HELP_OPTION_MOUNTIMAGE_IMAGEPATH, + [&](CommandLineInterface* commandLineInterface, const std::string& imagePath) + { + if (commandLineInterface->m_operationType != OperationType::MountImage) + { + return E_INVALIDARG; + } + commandLineInterface->m_mountImagePath = utf8_to_utf16(imagePath); + return S_OK; + }), + }, + { + L"-fileType", + Option(true, IDS_STRING_HELP_OPTION_MOUNT_FILETYPE, + [&](CommandLineInterface* commandLineInterface, const std::string& fileType) + { + if (commandLineInterface->m_operationType != OperationType::MountImage) + { + return E_INVALIDARG; + } + commandLineInterface->SetWVDFileType(utf8_to_utf16(fileType)); + return S_OK; + }), + }, + { + L"-readOnly", + Option(true, IDS_STRING_HELP_OPTION_MOUNT_READONLY, + [&](CommandLineInterface* commandLineInterface, const std::string& readOnly) + { + if (commandLineInterface->m_operationType != OperationType::MountImage) + { + return E_INVALIDARG; + } + if (CaseInsensitiveEquals(utf8_to_utf16(readOnly), std::wstring(L"true"))) + { + commandLineInterface->m_readOnly = true; + } + else if (CaseInsensitiveEquals(utf8_to_utf16(readOnly), std::wstring(L"false"))) + { + commandLineInterface->m_readOnly = false; + } + else + { + return E_INVALIDARG; + } + return S_OK; + }), + } + }) + }, + { + L"-UnmountImage", + Options(false, IDS_STRING_HELP_OPTION_UNMOUNTIMAGE, + [&](CommandLineInterface* commandLineInterface, const std::string&) + { + if (commandLineInterface->m_operationType != OperationType::Undefined) + { + return E_INVALIDARG; + } + commandLineInterface->m_operationType = OperationType::UnmountImage; + return S_OK; + }, + { + { + L"-imagePath", + Option(true, IDS_STRING_HELP_OPTION_MOUNTIMAGE_IMAGEPATH, + [&](CommandLineInterface* commandLineInterface, const std::string& imagePath) + { + if (commandLineInterface->m_operationType != OperationType::UnmountImage) + { + return E_INVALIDARG; + } + commandLineInterface->m_mountImagePath = utf8_to_utf16(imagePath); + return S_OK; + }), + }, + { + L"-volumeId", + Option(true, IDS_STRING_HELP_OPTION_UNMOUNTIMAGE_VOLUMEID, + [&](CommandLineInterface* commandLineInterface, const std::string& volumeId) + { + if (commandLineInterface->m_operationType != OperationType::UnmountImage) + { + return E_INVALIDARG; + } + commandLineInterface->m_volumeId = utf8_to_utf16(volumeId); + return S_OK; + }), + }, + { + L"-fileType", + Option(true, IDS_STRING_HELP_OPTION_MOUNT_FILETYPE, + [&](CommandLineInterface* commandLineInterface, const std::string& fileType) + { + if (commandLineInterface->m_operationType != OperationType::UnmountImage) + { + return E_INVALIDARG; + } + commandLineInterface->SetWVDFileType(utf8_to_utf16(fileType)); + return S_OK; + }), + } + }) + }, { L"-?", Options(false, IDS_STRING_HELP_OPTION_HELP, @@ -275,4 +459,24 @@ HRESULT CommandLineInterface::Init() } return S_OK; +} + +void CommandLineInterface::SetWVDFileType(std::wstring fileType) +{ + if (CaseInsensitiveEquals(fileType, L"VHD")) + { + this->m_fileType = WVDFileType::VHD; + } + else if (CaseInsensitiveEquals(fileType, L"VHDX")) + { + this->m_fileType = WVDFileType::VHDX; + } + else if (CaseInsensitiveEquals(fileType, L"CIM")) + { + this->m_fileType = WVDFileType::CIM; + } + else + { + this->m_fileType = WVDFileType::NotSpecified; + } } \ No newline at end of file diff --git a/MsixCore/msixmgr/CommandLineInterface.hpp b/MsixCore/msixmgr/CommandLineInterface.hpp index 34a749177..e01620406 100644 --- a/MsixCore/msixmgr/CommandLineInterface.hpp +++ b/MsixCore/msixmgr/CommandLineInterface.hpp @@ -11,7 +11,17 @@ enum OperationType Remove = 2, FindPackage = 3, Unpack = 5, - ApplyACLs = 6 + ApplyACLs = 6, + MountImage = 7, + UnmountImage = 8 +}; + +enum WVDFileType +{ + NotSpecified = 0, + VHD = 1, + VHDX = 2, + CIM = 3 }; class CommandLineInterface; @@ -79,13 +89,21 @@ class CommandLineInterface /// Displays contextual formatted help to the user used for command line tool void DisplayHelp(); HRESULT Init(); + void SetWVDFileType(std::wstring fileType); bool IsQuietMode() { return m_quietMode; } bool IsApplyACLs() { return m_applyACLs; } bool IsValidateSignature() { return m_validateSignature; } + bool IsCreate() { return m_create; } + bool isMountReadOnly() { return m_readOnly; } std::wstring GetPackageFilePathToInstall() { return m_packageFilePath; } std::wstring GetPackageFullName() { return m_packageFullName; } std::wstring GetUnpackDestination() { return m_unpackDestination; } + std::wstring GetRootDirectory() { return m_rootDirectory; } + std::wstring GetMountImagePath() { return m_mountImagePath; } + std::wstring GetVolumeId() { return m_volumeId; } + WVDFileType GetFileType() { return m_fileType; } OperationType GetOperationType() { return m_operationType; } + ULONGLONG GetVHDSize() { return m_vhdSize; } private: int m_argc = 0; char ** m_argv = nullptr; @@ -96,9 +114,16 @@ class CommandLineInterface std::wstring m_packageFilePath; std::wstring m_packageFullName; std::wstring m_unpackDestination; + std::wstring m_rootDirectory; + std::wstring m_mountImagePath; + std::wstring m_volumeId; bool m_quietMode; bool m_applyACLs; bool m_validateSignature; + bool m_create = false; + bool m_readOnly = true; + WVDFileType m_fileType = WVDFileType::NotSpecified; + ULONGLONG m_vhdSize = 0; OperationType m_operationType = OperationType::Undefined; diff --git a/MsixCore/msixmgr/UnpackProvider.cpp b/MsixCore/msixmgr/UnpackProvider.cpp index e4708f974..d02831f72 100644 --- a/MsixCore/msixmgr/UnpackProvider.cpp +++ b/MsixCore/msixmgr/UnpackProvider.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include "MsixErrors.hpp" using namespace MsixCoreLib; using namespace std; @@ -16,6 +18,121 @@ using namespace std; namespace MsixCoreLib { + // Purpose: + // - Unpacks either a single package/bundle or multiple packages/bundles from a specified + // source directory. + // + // Notes: + // - skippedFiles will be populated with the file paths of file objects in a specified + // source directory that are not packages or bundles. If all file objects in the directory + // are packages OR the given source path is for a single package/bundle, we expect that + // skippedFiles will be empty. + // - This function swallows errors from unpacking individual packages and bundles and adds + // the file paths of these packages/bundles to the failedPackages vector and adds the + // error code to the failedPackagesErrors vectors + HRESULT Unpack( + _In_ std::wstring source, + _In_ std::wstring destination, + _In_ bool isApplyACLs, + _In_ bool validateSignature, + _Inout_ std::vector &skippedFiles, + _Inout_ std::vector &failedPackages, + _Inout_ std::vector &failedPackagesErrors) + { + filesystem::path sourcePath(source); + bool isDirectory = filesystem::is_directory(sourcePath); + if (isDirectory) + { + RETURN_IF_FAILED(UnpackPackagesFromDirectory( + source, + destination, + isApplyACLs, + validateSignature, + skippedFiles, + failedPackages, + failedPackagesErrors)); + } + else + { + HRESULT hrUnpack = UnpackPackageOrBundle(source, destination, isApplyACLs, validateSignature); + if (FAILED(hrUnpack)) + { + failedPackages.push_back(source); + failedPackagesErrors.push_back(hrUnpack); + } + } + + return S_OK; + } + + // Purpose: + // - Unpacks the packages in the specified source directory to the given destination. + // + // Notes: + // - Files in the source directory that are not packages will be ignored and their file + // path will be added to the vector of skipped packages + // - Packages and bundles that fail to get unpacked will have their file path added to the vector + // of failedPackages and the associated error code will be added to the failedPackagesErrors + // vector at the corresponding index. + // - This function swallows errors from failing to unpack individual packages and should return + // S_OK in almost all cases, except when the given source path is not the path to a directory + HRESULT UnpackPackagesFromDirectory( + _In_ std::wstring source, + _In_ std::wstring destination, + _In_ bool isApplyACLs, + _In_ bool validateSignature, + _Inout_ std::vector &skippedFiles, + _Inout_ std::vector &failedPackages, + _Inout_ std::vector &failedPackagesErrors) + { + if (!filesystem::is_directory(filesystem::path(source))) + { + return E_INVALIDARG; + } + + for (const auto& entry : filesystem::directory_iterator(source)) + { + auto fullFilePath = entry.path().wstring(); + if (entry.is_regular_file()) + { + // Swallow the error and add to list of failed packages and their associated errors + HRESULT hrUnpack = UnpackPackageOrBundle(fullFilePath, destination, isApplyACLs, validateSignature); + if (FAILED(hrUnpack)) + { + failedPackages.push_back(fullFilePath); + failedPackagesErrors.push_back(hrUnpack); + } + } + else + { + skippedFiles.push_back(fullFilePath); + } + } + return S_OK; + } + + HRESULT UnpackPackageOrBundle( + _In_ std::wstring source, + _In_ std::wstring destination, + _In_ bool isApplyACLs, + _In_ bool validateSignature) + { + HRESULT hr = S_OK; + if (IsPackageFile(source)) + { + hr = MsixCoreLib::UnpackPackage(source, destination, isApplyACLs, validateSignature); + } + else if (IsBundleFile(source)) + { + hr = MsixCoreLib::UnpackBundle(source, destination, isApplyACLs, validateSignature); + } + else + { + return E_INVALIDARG; + } + return hr; + } + HRESULT UnpackPackage( _In_ std::wstring packageFilePath, _In_ std::wstring destination, diff --git a/MsixCore/msixmgr/UnpackProvider.hpp b/MsixCore/msixmgr/UnpackProvider.hpp index d4a4467d7..eeb4eb256 100644 --- a/MsixCore/msixmgr/UnpackProvider.hpp +++ b/MsixCore/msixmgr/UnpackProvider.hpp @@ -6,6 +6,30 @@ namespace MsixCoreLib { + HRESULT Unpack( + _In_ std::wstring source, + _In_ std::wstring destination, + _In_ bool isApplyACLs, + _In_ bool validateSignature, + _Inout_ std::vector &skippedFiles, + _Inout_ std::vector &failedPackages, + _Inout_ std::vector &failedPackagesErrors); + + HRESULT UnpackPackagesFromDirectory( + _In_ std::wstring source, + _In_ std::wstring destination, + _In_ bool isApplyACLs, + _In_ bool validateSignature, + _Inout_ std::vector &skippedFiles, + _Inout_ std::vector &failedPackages, + _Inout_ std::vector &failedPackagesErrors); + + HRESULT UnpackPackageOrBundle( + _In_ std::wstring source, + _In_ std::wstring destination, + _In_ bool isApplyACLs, + _In_ bool validateSignature); + HRESULT UnpackPackage( _In_ std::wstring packageFilePath, _In_ std::wstring destination, diff --git a/MsixCore/msixmgr/VHDProvider.cpp b/MsixCore/msixmgr/VHDProvider.cpp new file mode 100644 index 000000000..0d46421af --- /dev/null +++ b/MsixCore/msixmgr/VHDProvider.cpp @@ -0,0 +1,113 @@ +#include "MSIXWindows.hpp" +#include "VHDProvider.hpp" +#include "msixmgrLogger.hpp" +#include "..\msixmgrLib\GeneralUtil.hpp" +#include +#include +#include "InstallUI.hpp" + +using namespace MsixCoreLib; +using namespace std; + +namespace MsixCoreLib +{ + + class WVDUtilitiesDll + { + public: + WVDUtilitiesDll::WVDUtilitiesDll() + { + + } + + WVDUtilitiesDll::~WVDUtilitiesDll() + { + if (module != nullptr) + { + FreeLibrary(module); + } + } + + HRESULT load() + { + module = LoadLibrary(L"wvdutilities.dll"); + if (module == nullptr) + { + std::wcout << std::endl; + std::wcout << "Failed to load wvdutilities.dll. Please confirm the dll is next to this exe." << std::endl; + std::wcout << std::endl; + + return HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND); + } + return S_OK; + } + + HMODULE get() + { + return module; + } + + private: + HMODULE module; + }; + + HRESULT CreateAndMountVHD( + _In_ const std::wstring& vhdFilePath, + _In_ ULONGLONG sizeMBs, + _In_ bool isVHD, + _Inout_ std::wstring& driveLetter) + { + WVDUtilitiesDll wvdUtilities; + RETURN_IF_FAILED(wvdUtilities.load()); + + typedef HRESULT(STDMETHODCALLTYPE *CREATEANDMOUNTVHD)( + const std::wstring& vhdFilePath, + ULONGLONG sizeMBs, + bool isVHD, + std::wstring& driveLetter); + + CREATEANDMOUNTVHD CreateAndMountVHD = + reinterpret_cast + (GetProcAddress(wvdUtilities.get(), "CreateAndMountVHD")); + + RETURN_IF_FAILED(CreateAndMountVHD(vhdFilePath, sizeMBs, isVHD, driveLetter)); + + return S_OK; + } + + HRESULT UnmountVHD( + _In_ const std::wstring& vhdFilePath) + { + WVDUtilitiesDll wvdUtilities; + RETURN_IF_FAILED(wvdUtilities.load()); + + typedef HRESULT(STDMETHODCALLTYPE *UNMOUNTVHD)(const std::wstring& vhdFilePath); + + UNMOUNTVHD UnmountVHD = + reinterpret_cast + (GetProcAddress(wvdUtilities.get(), "UnmountVHD")); + + RETURN_IF_FAILED(UnmountVHD(vhdFilePath)); + + return S_OK; + } + + HRESULT MountVHD( + _In_ const std::wstring& vhdFilePath, + _In_ bool readOnly, + _Inout_ std::wstring& driveLetter) + { + WVDUtilitiesDll wvdUtilities; + RETURN_IF_FAILED(wvdUtilities.load()); + + typedef HRESULT(STDMETHODCALLTYPE *MOUNTVHD)(const std::wstring& vhdFilePath, bool readOnly, std::wstring& driveLetter); + + MOUNTVHD MountVHD = + reinterpret_cast + (GetProcAddress(wvdUtilities.get(), "MountVHD")); + + RETURN_IF_FAILED(MountVHD(vhdFilePath, readOnly, driveLetter)); + + return S_OK; + } +} \ No newline at end of file diff --git a/MsixCore/msixmgr/VHDProvider.hpp b/MsixCore/msixmgr/VHDProvider.hpp new file mode 100644 index 000000000..6144fd9f4 --- /dev/null +++ b/MsixCore/msixmgr/VHDProvider.hpp @@ -0,0 +1,19 @@ +#pragma once +#include + +namespace MsixCoreLib +{ + HRESULT CreateAndMountVHD( + _In_ const std::wstring& vhdFilePath, + _In_ ULONGLONG sizeMBs, + _In_ bool isVHD, + _Inout_ std::wstring& driveLetter); + + HRESULT UnmountVHD( + _In_ const std::wstring& vhdFilePath); + + HRESULT MountVHD( + _In_ const std::wstring& vhdFilePath, + _In_ bool readOnly, + _Inout_ std::wstring& driveLetter); +} \ No newline at end of file diff --git a/MsixCore/msixmgr/msixmgr.cpp b/MsixCore/msixmgr/msixmgr.cpp index c6edd6449..9fc02e249 100644 --- a/MsixCore/msixmgr/msixmgr.cpp +++ b/MsixCore/msixmgr/msixmgr.cpp @@ -19,7 +19,10 @@ #include #include "UnpackProvider.hpp" #include "ApplyACLsProvider.hpp" +#include "VHDProvider.hpp" +#include "CIMProvider.hpp" #include "MsixErrors.hpp" +#include #include using namespace std; @@ -119,6 +122,55 @@ void RelaunchAsAdmin(int argc, char * argv[]) ShellExecuteExW(&shellExecuteInfo); } +void OutputUnpackFailures( + _In_ std::wstring packageSource, + _In_ std::vector skippedFiles, + _In_ std::vector failedPackages, + _In_ std::vector failedPackagesErrors) +{ + if (!skippedFiles.empty()) + { + std::wcout << std::endl; + std::wcout << "[WARNING] The following items from " << packageSource << " were ignored because they are not packages or bundles " << std::endl; + std::wcout << std::endl; + + for (int i = 0; i < skippedFiles.size(); i++) + { + std::wcout << skippedFiles.at(i) << std::endl; + } + + std::wcout << std::endl; + } + + if (!failedPackages.empty()) + { + std::wcout << std::endl; + std::wcout << "[WARNING] The following packages from " << packageSource << " failed to get unpacked. Please try again: " << std::endl; + std::wcout << std::endl; + + for (int i = 0; i < failedPackages.size(); i++) + { + HRESULT hr = failedPackagesErrors.at(i); + + std::wcout << L"Failed with HRESULT 0x" << std::hex << hr << L" when trying to unpack " << failedPackages.at(i) << std::endl; + if (hr == static_cast(MSIX::Error::CertNotTrusted)) + { + std::wcout << L"Please confirm that the certificate has been installed for this package" << std::endl; + } + else if (hr == static_cast(MSIX::Error::FileWrite)) + { + std::wcout << L"The tool encountered a file write error. If you are unpacking to a VHD, please try again with a larger VHD, as file write errors may be caused by insufficient disk space." << std::endl; + } + else if (hr == E_INVALIDARG) + { + std::wcout << "Please confirm the given package path is an .appx, .appxbundle, .msix, or .msixbundle file" << std::endl; + } + + std::wcout << std::endl; + } + } +} + int main(int argc, char * argv[]) { // Register the providers @@ -192,13 +244,13 @@ int main(int argc, char * argv[]) } else { - if (!isAdmin) - { - RelaunchAsAdmin(argc, argv); - return 0; - } - auto ui = new UI(packageManager, cli.GetPackageFilePathToInstall(), UIType::InstallUIAdd); - ui->ShowUI(); + if (!isAdmin) + { + RelaunchAsAdmin(argc, argv); + return 0; + } + auto ui = new UI(packageManager, cli.GetPackageFilePathToInstall(), UIType::InstallUIAdd); + ui->ShowUI(); } } break; @@ -246,7 +298,7 @@ int main(int argc, char * argv[]) } std::cout << numPackages << " Package(s) found" << std::endl; - } + } return S_OK; } @@ -254,54 +306,356 @@ int main(int argc, char * argv[]) { HRESULT hr = S_OK; - auto packageFilePath = cli.GetPackageFilePathToInstall(); + auto packageSourcePath = cli.GetPackageFilePathToInstall(); auto unpackDestination = cli.GetUnpackDestination(); + auto rootDirectory = cli.GetRootDirectory(); + WVDFileType fileType = cli.GetFileType(); + bool createFile = cli.IsCreate(); - if (IsPackageFile(packageFilePath)) + std::vector skippedFiles; + std::vector failedPackages; + std::vector failedPackagesErrors; + + if (fileType == WVDFileType::CIM) { - hr = MsixCoreLib::UnpackPackage(packageFilePath, unpackDestination, cli.IsApplyACLs(), cli.IsValidateSignature()); + if (rootDirectory.empty() || fileType == WVDFileType::NotSpecified) + { + std::wcout << std::endl; + std::wcout << "Creating a file with the -create option requires both a -rootDirectory and -fileType" << std::endl; + std::wcout << std::endl; + return E_INVALIDARG; + } + if (!EndsWith(unpackDestination, L".cim")) + { + std::wcout << std::endl; + std::wcout << "Invalid CIM file name. File name must have .cim file extension" << std::endl; + std::wcout << std::endl; + return E_INVALIDARG; + } + + // Create a temporary directory to unpack package(s) since we cannot unpack to the CIM directly. + std::wstring currentDirectory = std::filesystem::current_path(); + std::wstring uniqueIdString; + RETURN_IF_FAILED(CreateGUIDString(&uniqueIdString)); + std::wstring tempDirPathString = currentDirectory + L"\\" + uniqueIdString; + std::filesystem::path tempDirPath(tempDirPathString); + + std::error_code createDirectoryErrorCode; + bool createTempDirResult = std::filesystem::create_directory(tempDirPath, createDirectoryErrorCode); + + // Since we're using a GUID, this should almost never happen + if (!createTempDirResult) + { + std::wcout << std::endl; + std::wcout << "Failed to create temp directory " << tempDirPathString << std::endl; + std::wcout << "This may occur when the directory path already exists. Please try again." << std::endl; + std::wcout << std::endl; + return E_UNEXPECTED; + } + if (createDirectoryErrorCode.value() != 0) + { + // Again, we expect that the creation of the temp directory will fail very rarely. Output the exception + // and have the user try again. + std::wcout << std::endl; + std::wcout << "Creation of temp directory " << tempDirPathString << " failed with error: " << createDirectoryErrorCode.value() << std::endl; + std::cout << "Error message: " << createDirectoryErrorCode.message() << std::endl; + std::wcout << "Please try again." << std::endl; + std::wcout << std::endl; + return E_UNEXPECTED; + } + + RETURN_IF_FAILED(MsixCoreLib::Unpack( + packageSourcePath, + tempDirPathString, + cli.IsApplyACLs(), + cli.IsValidateSignature(), + skippedFiles, + failedPackages, + failedPackagesErrors)); + + HRESULT hrCreateCIM = MsixCoreLib::CreateAndAddToCIM(unpackDestination, tempDirPathString, rootDirectory); + + // Best-effort attempt to remove temp directory + std::error_code removeTempDirErrorCode; + bool removeTemprDirResult = std::filesystem::remove_all(tempDirPath, removeTempDirErrorCode); + if (!removeTemprDirResult || removeTempDirErrorCode.value() != 0) + { + std::wcout << std::endl; + std::wcout << "Failed to remove the temp dir " << tempDirPath << std::endl; + std::wcout << "Ignoring this non-fatal error and moving on" << std::endl; + std::wcout << std::endl; + } + + if (FAILED(hrCreateCIM)) + { + std::wcout << std::endl; + std::wcout << "Creating the CIM file " << unpackDestination << " failed with HRESULT 0x" << std::hex << hrCreateCIM << std::endl; + std::wcout << std::endl; + return hrCreateCIM; + } + else + { + std::wcout << std::endl; + std::wcout << "Successfully created the CIM file: " << unpackDestination << std::endl; + std::wcout << std::endl; + + OutputUnpackFailures(packageSourcePath, skippedFiles, failedPackages, failedPackagesErrors); + } + } - else if (IsBundleFile(packageFilePath)) + // UnpackDestinationFileType::NotSpecified is only valid if unpacking to an existing VHD + else if (fileType == WVDFileType::NotSpecified || fileType == WVDFileType::VHD || fileType == WVDFileType::VHDX) { - hr = MsixCoreLib::UnpackBundle(packageFilePath, unpackDestination, cli.IsApplyACLs(), cli.IsValidateSignature()); + if (createFile) + { + if (!(EndsWith(unpackDestination, L".vhd") || (EndsWith(unpackDestination, L".vhdx")))) + { + std::wcout << std::endl; + std::wcout << "Invalid VHD file name. File name must have .vhd or .vhdx file extension" << std::endl; + std::wcout << std::endl; + return E_INVALIDARG; + } + else + { + if (cli.GetVHDSize() == 0) + { + std::wcout << std::endl; + std::wcout << "VHD size was not specified. Please provide a vhd size in MB using the -vhdSize option" << std::endl; + std::wcout << std::endl; + return E_INVALIDARG; + } + + std::wstring driveLetter; + HRESULT hrCreateVHD = MsixCoreLib::CreateAndMountVHD(unpackDestination, cli.GetVHDSize(), fileType == WVDFileType::VHD, driveLetter); + if (FAILED(hrCreateVHD)) + { + std::wcout << std::endl; + std::wcout << "Creating the VHD(X) file " << unpackDestination << " failed with HRESULT 0x" << std::hex << hrCreateVHD << std::endl; + + if (hrCreateVHD != HRESULT_FROM_WIN32(ERROR_FILE_EXISTS)) + { + // Best effort to unmount and delete the VHD file + if (std::filesystem::exists(std::filesystem::path(unpackDestination.c_str()))) + { + MsixCoreLib::UnmountVHD(unpackDestination); + if (_wremove(unpackDestination.c_str()) != 0) + { + std::wcout << "Failed best-effort attempt to delete the incomplete VHD(X) file: " << unpackDestination << " Please do not use this file." << std::endl; + } + else + { + std::wcout << "Best-effort attempt to delete the incomplete VHD(X) file " << unpackDestination << " succeeded." << std::endl; + } + } + } + + std::wcout << std::endl; + + return hrCreateVHD; + } + + // Unpack to the mounted VHD + std::wstring mountedUnpackDest = driveLetter + L":\\" + cli.GetRootDirectory(); + RETURN_IF_FAILED(MsixCoreLib::Unpack( + packageSourcePath, + mountedUnpackDest, + cli.IsApplyACLs(), + cli.IsValidateSignature(), + skippedFiles, + failedPackages, + failedPackagesErrors + )); + + HRESULT hrUnmount = MsixCoreLib::UnmountVHD(unpackDestination); + if (FAILED(hrUnmount)) + { + std::wcout << std::endl; + std::wcout << "Unmounting the VHD " << unpackDestination << " failed with HRESULT 0x" << std::hex << hrCreateVHD << std::endl; + std::wcout << "Ignoring as non-fatal error.." << std::endl; + std::wcout << std::endl; + } + + OutputUnpackFailures(packageSourcePath, skippedFiles, failedPackages, failedPackagesErrors); + + std::wcout << std::endl; + std::wcout << "Finished unpacking packages to: " << unpackDestination << std::endl; + std::wcout << std::endl; + } + } + else + { + RETURN_IF_FAILED(MsixCoreLib::Unpack( + packageSourcePath, + unpackDestination, + cli.IsApplyACLs(), + cli.IsValidateSignature(), + skippedFiles, + failedPackages, + failedPackagesErrors)); + + std::wcout << std::endl; + std::wcout << "Finished unpacking packages to: " << unpackDestination << std::endl; + std::wcout << std::endl; + + OutputUnpackFailures(packageSourcePath, skippedFiles, failedPackages, failedPackagesErrors); + } } - else + return hr; + } + case OperationType::ApplyACLs: + { + std::vector packageFolders; + packageFolders.push_back(cli.GetPackageFilePathToInstall()); // we're not actually installing anything. The API just returns the file path name we need. + RETURN_IF_FAILED(MsixCoreLib::ApplyACLs(packageFolders)); + return S_OK; + } + case OperationType::MountImage: + { + WVDFileType fileType = cli.GetFileType(); + + if (cli.GetMountImagePath().empty()) { std::wcout << std::endl; - std::wcout << "Invalid package path: " << packageFilePath << std::endl; - std::wcout << "Please confirm the given package path is an .appx, .appxbundle, .msix, or .msixbundle file" << std::endl; + std::wcout << "Please provide the path to the image you would like to mount." << std::endl; std::wcout << std::endl; return E_INVALIDARG; } - if (FAILED(hr)) + + if (fileType == WVDFileType::CIM) { - std::wcout << std::endl; - std::wcout << L"Failed with HRESULT 0x" << std::hex << hr << L" when trying to unpack " << packageFilePath << std::endl; - if (hr == static_cast(MSIX::Error::CertNotTrusted)) + std::wstring volumeId; + HRESULT hrMountCIM = MsixCoreLib::MountCIM(cli.GetMountImagePath(), volumeId); + if (FAILED(hrMountCIM)) { - std::wcout << L"Please confirm that the certificate has been installed for this package" << std::endl; + std::wcout << std::endl; + std::wcout << "Mounting the CIM file " << cli.GetMountImagePath() << " failed with HRESULT 0x" << std::hex << hrMountCIM << std::endl; + std::wcout << std::endl; + return hrMountCIM; } - else if (hr == static_cast(MSIX::Error::FileWrite)) + else { - std::wcout << L"The tool encountered a file write error. If you are unpacking to a VHD, please try again with a larger VHD, as file write errors may be caused by insufficient disk space." << std::endl; + std::wcout << std::endl; + std::wcout << "Image successfully mounted!" << std::endl; + std::wcout << "To examine contents in File Explorer, press Win + R and enter the following: " << std::endl; + std::wcout << "\\\\?\\Volume{" << volumeId << "}" << std::endl; + std::wcout << std::endl; + std::wcout << "To unmount, run one of the followings commands: " << std::endl; + std::wcout << "msixmgr.exe -unmountimage -imagePath " << cli.GetMountImagePath() << " -filetype CIM" << std::endl; + std::wcout << "msixmgr.exe -unmountimage -volumeid " << volumeId << " -filetype CIM" << std::endl; + std::wcout << std::endl; + } + } + else if (fileType == WVDFileType::VHD || fileType == WVDFileType::VHDX) + { + std::wstring driveLetter; + HRESULT hrMountVHD = MsixCoreLib::MountVHD(cli.GetMountImagePath(), cli.isMountReadOnly(), driveLetter); + if (FAILED(hrMountVHD)) + { + std::wcout << std::endl; + std::wcout << "Mounting the VHD(X) file " << cli.GetMountImagePath() << " failed with HRESULT 0x" << std::hex << hrMountVHD << std::endl; + std::wcout << std::endl; + return hrMountVHD; + } + else + { + bool isVHD = cli.GetFileType() == WVDFileType::VHD; + std::wcout << std::endl; + std::wcout << "Image " << cli.GetMountImagePath() << " successfully mounted to " << driveLetter << ":\\" << std::endl; + std::wcout << "To unmount, run the following command: " << std::endl; + std::wcout << "msixmgr.exe -unmountimage -imagePath " << cli.GetMountImagePath() << " -filetype VHD" << (isVHD ? "" : "X") << std::endl; + std::wcout << std::endl; } - std::wcout << std::endl; } else { std::wcout << std::endl; - std::wcout << "Successfully unpacked and applied ACLs for package: " << packageFilePath << std::endl; - std::wcout << "If your package is a store-signed package, please note that store-signed apps require a license file to be included, which can be downloaded from the Microsoft Store for Business" << std::endl; + std::wcout << "Please specify one of the following supported file types for the -MountImage command: {VHD, VHDX, CIM}" << std::endl; std::wcout << std::endl; + return ERROR_NOT_SUPPORTED; } - - return hr; + return S_OK; } - case OperationType::ApplyACLs: + case OperationType::UnmountImage: { - std::vector packageFolders; - packageFolders.push_back(cli.GetPackageFilePathToInstall()); // we're not actually installing anything. The API just returns the file path name we need. - RETURN_IF_FAILED(MsixCoreLib::ApplyACLs(packageFolders)); + WVDFileType fileType = cli.GetFileType(); + if (fileType == WVDFileType::CIM) + { + if (cli.GetVolumeId().empty() && cli.GetMountImagePath().empty()) + { + std::wcout << std::endl; + std::wcout << "To unmount an CIM image, please provide either the CIM file path or the volume the image was mounted to." << std::endl; + std::wcout << "The CIM file path can be specified using the -imagepath option." << std::endl; + std::wcout << "The volume can be specified using the -volumeId option." << std::endl; + std::wcout << std::endl; + return E_INVALIDARG; + } + + HRESULT hrUnmountCIM = MsixCoreLib::UnmountCIM(cli.GetMountImagePath(), cli.GetVolumeId()); + + if (FAILED(hrUnmountCIM)) + { + std::wcout << std::endl; + std::wcout << "Unmounting the CIM " << " failed with HRESULT 0x" << std::hex << hrUnmountCIM << std::endl; + + // ERROR_NOT_FOUND may be returned if only the mount image path but not the volume id was provided + // and msixmgr was unable to find the volume id associated with a given image path. + if (hrUnmountCIM == HRESULT_FROM_WIN32(ERROR_NOT_FOUND) && cli.GetVolumeId().empty()) + { + std::wcout << "The error ERROR_NOT_FOUND may indicate a failure to find the volume id associated with a given image path."<< std::endl; + std::wcout << "Please try unmounting using the -volumeId option." << std::endl; + } + + std::wcout << std::endl; + return hrUnmountCIM; + } + else + { + std::wcout << std::endl; + if (!cli.GetMountImagePath().empty()) + { + std::wcout << "Successfully unmounted the CIM file: " << cli.GetMountImagePath() << std::endl; + } + else + { + std::wcout << "Successfully unmounted the CIM with volume id: " << cli.GetVolumeId() << std::endl; + } + + std::wcout << std::endl; + } + } + else if (fileType == WVDFileType::VHD || fileType == WVDFileType::VHDX) + { + if (cli.GetMountImagePath().empty()) + { + std::wcout << std::endl; + std::wcout << "Please provide the path to the image you would like to unmount." << std::endl; + std::wcout << std::endl; + return E_INVALIDARG; + } + + HRESULT hrUnmountVHD = MsixCoreLib::UnmountVHD(cli.GetMountImagePath()); + + if (FAILED(hrUnmountVHD)) + { + std::wcout << std::endl; + std::wcout << "Unmounting the VHD " << cli.GetMountImagePath() << " failed with HRESULT 0x" << std::hex << hrUnmountVHD << std::endl; + std::wcout << std::endl; + return hrUnmountVHD; + } + else + { + std::wcout << std::endl; + std::wcout << "Successfully unmounted the VHD " << cli.GetMountImagePath() << std::endl; + std::wcout << std::endl; + } + } + else + { + std::wcout << std::endl; + std::wcout << "Please specify one of the following supported file types for the -UnmountImage command: {VHD, VHDX, CIM}" << std::endl; + std::wcout << std::endl; + return ERROR_NOT_SUPPORTED; + } return S_OK; } default: diff --git a/MsixCore/msixmgr/msixmgr.rc b/MsixCore/msixmgr/msixmgr.rc index bcbffae69..659095679 100644 --- a/MsixCore/msixmgr/msixmgr.rc +++ b/MsixCore/msixmgr/msixmgr.rc @@ -75,7 +75,7 @@ BEGIN IDS_STRING_HELP_OPTION_FINDPACKAGE "Find package with the specific package full name." IDS_STRING_HELP_OPTION_HELP "Display this help text." - IDS_STRING_HELPTEXT_USAGE " Usage:\n ------\n \tmsixmgr.exe [options]\n \tmsixmgr.exe -Unpack -packagePath -destination [-applyacls]\n \tmsixmgr.exe -ApplyACLs -packagePath \n\n" + IDS_STRING_HELPTEXT_USAGE " Usage:\n ------\n \tmsixmgr.exe [options]\n \tmsixmgr.exe -Unpack -packagePath -destination [-applyacls] [-create] [-vhdSize ] [-filetype ] [-rootDirectory ] \n \tmsixmgr.exe -ApplyACLs -packagePath \n \tmsixmgr.exe -MountImage -imagePath -fileType [ VHD | VHDX | CIM ]\n \tmsixmgr.exe -UnmountImage -imagePath -fileType [ VHD | VHDX | CIM ]\n\n" IDS_STRING_UI_CANCEL "Cancel" END @@ -98,10 +98,10 @@ BEGIN IDS_STRING_HELPTEXT_DESCRIPTION " Description:\n -----------\n \tInstalls an msix package, removes an msix package or queries for msix packages.\n \tMay also unpack msix packages and apply ACLs to the resulting package folders\n\n" IDS_STRING_HELPTEXT_OPTIONS " Options:\n -------\n" IDS_STRING_HELP_OPTION_UNPACK "Unpack a package (.appx, .msix, .appxbundle, .msixbundle) and extract its contents to a folder." - IDS_STRING_HELP_OPTION_UNPACK_PACKAGEPATH "the path to the package to unpack" + IDS_STRING_HELP_OPTION_UNPACK_PACKAGEPATH "the path to the package to unpack OR the path to a directory containing multiple packages to unpack" IDS_STRING_HELP_OPTION_UNPACK_DESTINATION "the directory to place the resulting package folder(s) in" IDS_STRING_HELP_OPTION_UNPACK_APPLYACLS "optional parameter that applies ACLs to the resulting package folder(s) and their parent folder" - IDS_STRING_HELP_OPTION_UNPACK_VALIDATESIGNATURE "optional parameter that validates a package's signature file before unpacking the package. This will require that the package's certificate is installed on the machine." + IDS_STRING_HELP_OPTION_UNPACK_VALIDATESIGNATURE "optional parameter that validates a package's signature file before unpacking the package. This will require that the package's certificate is installed on the machine." IDS_STRING_HELP_OPTION_APPLYACLS "Applies ACLs to a package folder (an unpacked package)" IDS_STRING_HELP_OPTION_APPLYACLS_PACKAGEPATH "the path to the folder to apply acls to" IDS_STRING_LOADING_PACKAGE_ERROR "installation failed" @@ -124,6 +124,16 @@ BEGIN IDS_STRING_INVALID_ARCHITECTURE_ERROR "The deployment operation failed because the package targets the wrong processor architecture." IDS_STRING_PACKAGE_DOWNGRADE_ERROR "The package could not be installed because a higher version of this package is already installed." IDS_STRING_USER_CANCELLED_INSTALL_ERROR "User cancelled installation." + IDS_STRING_HELP_OPTION_UNPACK_CREATE "optional parameter that creates a new image with the specified -filetype and unpacks the packages to that image" + IDS_STRING_HELP_OPTION_UNPACK_ROOTDIRECTORY "root directory on an image to unpack packages to. Required parameter for unpacking to new and existing CIM files" + IDS_STRING_HELP_OPTION_UNPACK_FILETYPE "the type of file to unpack packages to. Valid file types include {VHD, VHDX, CIM}. This is a required parameter when unpacking to CIM files" + IDS_STRING_HELP_OPTION_MOUNTIMAGE "Mounts the VHD, VHDX, or CIM image." + IDS_STRING_HELP_OPTION_MOUNTIMAGE_IMAGEPATH "the path to the image file to mount or unmount." + IDS_STRING_HELP_OPTION_UNMOUNTIMAGE "Unmounts the VHD, VHDX, or CIM image" + IDS_STRING_HELP_OPTION_UNMOUNTIMAGE_VOLUMEID "the GUID (specified without curly braces) associated with the image to unmount. This is an optional parameter only for CIM files." + IDS_STRING_HELP_OPTION_MOUNT_FILETYPE "the type of file to mount or unmount. The following file types are currently supported: {VHD, VHDX, CIM}" + IDS_STRING_HELP_OPTION_UNPACK_VHDSIZE "the desired size of the VHD or VHDX file in MB. Must be between 5 and 2040000 MB. Use only for VHD or VHDX files" + IDS_STRING_HELP_OPTION_MOUNT_READONLY "boolean (true of false) indicating whether a VHD(X) should be mounted as read only. If not specified, the image is mounted as read-only by default" END diff --git a/MsixCore/msixmgr/msixmgr.vcxproj b/MsixCore/msixmgr/msixmgr.vcxproj index 88d122bcf..9260b0bfb 100644 --- a/MsixCore/msixmgr/msixmgr.vcxproj +++ b/MsixCore/msixmgr/msixmgr.vcxproj @@ -208,7 +208,7 @@ del "$(OutDir)msixmgr_LN.exe" ..\x64\Release;..\..\.vs\lib;%(AdditionalLibraryDirectories) AsInvoker comctl32.lib %(AdditionalOptions) - msixmgrlib.lib;msix.lib;msi.lib;comctl32.lib;uxtheme.lib;gdiplus.lib;version.lib;taskschd.lib;windowsapp_downlevel.lib;windowsapp.lib;urlmon.lib;%(AdditionalDependencies) + msixmgrlib.lib;msix.lib;msi.lib;comctl32.lib;uxtheme.lib;gdiplus.lib;version.lib;taskschd.lib;windowsapp_downlevel.lib;windowsapp.lib;urlmon.lib;virtdisk.lib;Shlwapi.lib;%(AdditionalDependencies) api-ms-win-core-winrt-string-l1-1-0.dll;api-ms-win-core-winrt-l1-1-0.dll;api-ms-win-core-winrt-error-l1-1-0.dll;api-ms-win-core-winrt-error-l1-1-1.dll @@ -222,24 +222,31 @@ del "$(OutDir)msixmgr_LN.exe" WindowsCompatibility.manifest %(AdditionalManifestFiles) + + false + + + + + diff --git a/MsixCore/msixmgr/msixmgr.vcxproj.filters b/MsixCore/msixmgr/msixmgr.vcxproj.filters index e38d482ed..c468111fc 100644 --- a/MsixCore/msixmgr/msixmgr.vcxproj.filters +++ b/MsixCore/msixmgr/msixmgr.vcxproj.filters @@ -36,6 +36,12 @@ Header Files + + Header Files + + + Header Files + @@ -59,6 +65,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/MsixCore/msixmgr/resource.h b/MsixCore/msixmgr/resource.h index 69cca515e..b81409bb7 100644 --- a/MsixCore/msixmgr/resource.h +++ b/MsixCore/msixmgr/resource.h @@ -56,6 +56,16 @@ #define IDS_STRING_INVALID_ARCHITECTURE_ERROR 151 #define IDS_STRING_PACKAGE_DOWNGRADE_ERROR 152 #define IDS_STRING_USER_CANCELLED_INSTALL_ERROR 153 +#define IDS_STRING_HELP_OPTION_UNPACK_CREATE 154 +#define IDS_STRING_HELP_OPTION_UNPACK_ROOTDIRECTORY 155 +#define IDS_STRING_HELP_OPTION_UNPACK_FILETYPE 156 +#define IDS_STRING_HELP_OPTION_MOUNTIMAGE 157 +#define IDS_STRING_HELP_OPTION_MOUNTIMAGE_IMAGEPATH 158 +#define IDS_STRING_HELP_OPTION_UNMOUNTIMAGE 159 +#define IDS_STRING_HELP_OPTION_UNMOUNTIMAGE_VOLUMEID 160 +#define IDS_STRING_HELP_OPTION_MOUNT_FILETYPE 161 +#define IDS_STRING_HELP_OPTION_UNPACK_VHDSIZE 162 +#define IDS_STRING_HELP_OPTION_MOUNT_READONLY 163 // Next default values for new objects // diff --git a/MsixCore/msixmgrLib/GeneralUtil.cpp b/MsixCore/msixmgrLib/GeneralUtil.cpp index 1aef8ae7b..f1544ec59 100644 --- a/MsixCore/msixmgrLib/GeneralUtil.cpp +++ b/MsixCore/msixmgrLib/GeneralUtil.cpp @@ -350,4 +350,22 @@ namespace MsixCoreLib return S_OK; } + + HRESULT CreateGUIDString(std::wstring* guidString) + { + GUID guid; + RPC_STATUS status = UuidCreate(&guid); + if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) + { + return HRESULT_FROM_WIN32(status); + } + + RPC_WSTR uniqueIDRPCString = NULL; + if (UuidToStringW(&guid, &uniqueIDRPCString) == RPC_S_OK) + { + *guidString = (WCHAR*) uniqueIDRPCString; + RpcStringFreeW(&uniqueIDRPCString); + } + return S_OK; + } } \ No newline at end of file diff --git a/MsixCore/msixmgrLib/GeneralUtil.hpp b/MsixCore/msixmgrLib/GeneralUtil.hpp index 98e6ca5c4..cdc460587 100644 --- a/MsixCore/msixmgrLib/GeneralUtil.hpp +++ b/MsixCore/msixmgrLib/GeneralUtil.hpp @@ -108,6 +108,9 @@ namespace MsixCoreLib /// @param isUnversioned - whether the file does not have a version HRESULT GetFileVersion(std::wstring file, _Out_ UINT64& version, _Out_ bool& isUnversioned); + /// Creates GUID string + HRESULT CreateGUIDString(std::wstring* guidString); + // // Stripped down ComPtr class provided for those platforms that do not already have a ComPtr class. // diff --git a/README.md b/README.md index e73b9924b..8b5d3c11b 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ The MSIX Packaging APIs that a client app would use to interact with .msix/.appx packages are a subset of those documented [here](https://msdn.microsoft.com/en-us/library/windows/desktop/hh446766(v=vs.85).aspx). - An example of such a client app is the [MSIX Core project](MsixCore/README.md), which installs .msix/.appx packages on Windows 7 SP1 and later versions of Windows. - ## Overview The MSIX SDK project includes cross platform API support for packing and unpacking of .msix/.appx packages @@ -17,6 +15,7 @@ The MSIX SDK project includes cross platform API support for packing and unpacki |--------------------------------------|---------------------------------| | **msix** | A shared library (DLL on Win32, dylib on macOS, SO on Linux and Android) that exports a subset of the functionality contained within appxpackaging.dll on Windows. See [here](https://msdn.microsoft.com/en-us/library/windows/desktop/hh446766(v=vs.85).aspx) for additional details.
On all platforms instead of CoCreating IAppxFactory, a C-style export: CoCreateAppxFactory is provided. Similarly, the CoCreateAppxBundleFactory export is equivalent as CoCreating IAppxBundleFactory.

The 'UnpackPackage' and 'UnpackBundle' exports that provide a simplified unpackage implementation. Similarly, PackPackage provides a simplified package implementation. See the [samples directory](sample) for usage of the SDK.| | **makemsix** | A command line wrapper over the MSIX library entrypoints. makemsix supports pack and unpack. Use the -? to get information about the options supported.| +| **MSIX Core** | A client app that uses installs .msix/.appx packages on Windows 7 SP1 and later versions of Windows. Go to the [MSIX Core project](MsixCore/README.md) page, to get more details.| Guidance on how to package your app contents and construct your app manifest such that it can take advantage of the cross platform support of this SDK is [here](tdf-guidance.md). @@ -153,7 +152,7 @@ The following native platforms are in development now: **Release x32 With Pack and OpenSSL**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_32_sign)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)| **Release x64 With Pack and OpenSSL**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_64_sign)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)| -Built in the Azure Pipelines Hosted VS2017 pool. See specifications [here](https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/win/Vs2017-Server2016-Readme.md) +Built in the Azure Pipelines windows-latest pool. See specifications [here](https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md) ### macOS ||master| @@ -163,8 +162,16 @@ Built in the Azure Pipelines Hosted VS2017 pool. See specifications [here](https **Release Without Bundle support**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20release_nobundle)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| **Debug With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20debug_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| **Release With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20release_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| +**Debug arm64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20debug_nopack_arm64)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| +**Release arm64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20release_nopack_arm64)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| +**Release Without Bundle support arm64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20release_nobundle_arm64)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| +**Debug With Pack arm64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20debug_pack_arm64)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| +**Release With Pack arm64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20release_pack_arm64)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| +**Release Universal**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS_universal_nopack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| +**Release Without Bundle support Universal**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS_universal_nobundle)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| +**Release With Pack Universal**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macO_universal_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)| -Built in the Azure Pipelines macOS pool. See specification [here](https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/macos/macos-10.14-Readme.md) +Built in the Azure Pipelines macOS pool. See specification [here](https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md) ### iOS ||master| @@ -210,6 +217,12 @@ We also produce msix-jni.jar which acts as a helper to get the languages from th The default level for the SDK level is 24 because we use the [Configuration class](https://developer.android.com/reference/android/content/res/Configuration) and, depending on the version of the device, we either use the locale attribute (deprecated as of API level 24) or getLocales. We recommend using the [makeaosp](makeaosp) script to build for Android on non-Windows devices. +## Apple Silicon +To enable building the MSIX SDK to run on Apple Silicon do the following: +1. Install Xcode beta 12 (https://developer.apple.com/download/) +2. Change active developer directory `sudo xcode-select -switch /Applications/Xcode-beta.app/Contents/Developer` +3. Build using makemac.sh `./makemac.sh -arch arm64 --skip-tests` + ## Testing msixtest uses Catch2 as testing framework. msixtest is either an executable or a shared library, depending on the platform. It has a single entrypoint msixtest_main that takes argc and argv, as main, plus the path were the test packages are located. The shared library is used for our mobile test apps, while non-mobile just forwards the arguments to msixtest_main. It requires msix.dll to be build with "Release" or "RelWithDebInfo" CMake switch. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 000000000..f355432b5 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,9 @@ +# Support # + +### How to file issues and get help ### +This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a [new Issue](https://github.com/microsoft/msix-packaging/issues/new/choose). + +For help and questions about using this project, please contact the MSIX Packaging team at MSIXPackagingOSSCustomerQs@service.microsoft.com + +### Microsoft Support Policy ### +Support for this project is limited to the resources listed above. \ No newline at end of file diff --git a/cmake/macos.cmake b/cmake/macos.cmake new file mode 100644 index 000000000..68f3800ca --- /dev/null +++ b/cmake/macos.cmake @@ -0,0 +1,19 @@ +# This is a check to fail arm64 build if the Xcode version is not adequate. + +# Get xcode version and build. +execute_process(COMMAND xcodebuild -version OUTPUT_VARIABLE XCODE_OUTPUT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +string(REGEX MATCH "Xcode [0-9\\.]+" XCODE_VERSION "${XCODE_OUTPUT}") +string(REGEX REPLACE "Xcode ([0-9\\.]+)" "\\1" XCODE_VERSION "${XCODE_VERSION}") +string(REGEX MATCH "Build version [a-zA-Z0-9]+" XCODE_BUILD "${XCODE_OUTPUT}") +string(REGEX REPLACE "Build version ([a-zA-Z0-9]+)" "\\1" XCODE_BUILD "${XCODE_BUILD}") +message(STATUS "Using Xcode version: ${XCODE_VERSION}") +message(STATUS "Using Xcode build: ${XCODE_BUILD}") + +if (CMAKE_OSX_ARCHITECTURES MATCHES "arm64") + # arm64 is only supported for version xcode 12 and higher + if (XCODE_VERSION LESS 12.0) + # If you see this and you have Xcode-beta 12 do: + # sudo xcode-select -switch /Applications/Xcode-beta.app/Contents/Developer + message(FATAL_ERROR "arm64 is only supported on Xcode 12 at this time. Found version ${XCODE_VERSION}. To enable arm64 builds please download Xcode beta 12 and run `sudo xcode-select -switch /Applications/Xcode-beta.app/Contents/Developer`") + endif() +endif() diff --git a/cmake/msix_resources.cmake b/cmake/msix_resources.cmake index 08f290770..ed0678f23 100644 --- a/cmake/msix_resources.cmake +++ b/cmake/msix_resources.cmake @@ -117,10 +117,18 @@ if ((XML_PARSER MATCHES msxml6) OR (XML_PARSER MATCHES xerces)) "http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" "desktop5" "AppxPackaging/Manifest/Schema/2018/DesktopManifestSchema_v5.xsd") list(APPEND MANIFEST_DESK6 "http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" "desktop6" "AppxPackaging/Manifest/Schema/2018/DesktopManifestSchema_v6.xsd") + list(APPEND MANIFEST_DESK7 + "http://schemas.microsoft.com/appx/manifest/desktop/windows10/7" "desktop7" "AppxPackaging/Manifest/Schema/2020/DesktopManifestSchema_v7.xsd") + list(APPEND MANIFEST_DESK8 + "http://schemas.microsoft.com/appx/manifest/desktop/windows10/8" "desktop8" "AppxPackaging/Manifest/Schema/2021/DesktopManifestSchema_v8.xsd") list(APPEND MANIFEST_COM "http://schemas.microsoft.com/appx/manifest/com/windows10" "com" "AppxPackaging/Manifest/Schema/2015/ComManifestSchema.xsd") list(APPEND MANIFEST_COM2 "http://schemas.microsoft.com/appx/manifest/com/windows10/2" "com2" "AppxPackaging/Manifest/Schema/2017/ComManifestSchema_v2.xsd") + list(APPEND MANIFEST_COM3 + "http://schemas.microsoft.com/appx/manifest/com/windows10/3" "com3" "AppxPackaging/Manifest/Schema/2019/ComManifestSchema_v3.xsd") + list(APPEND MANIFEST_COM4 + "http://schemas.microsoft.com/appx/manifest/com/windows10/4" "com4" "AppxPackaging/Manifest/Schema/2020/ComManifestSchema_v4.xsd") list(APPEND MANIFEST_UAP5 "http://schemas.microsoft.com/appx/manifest/uap/windows10/5" "uap5" "AppxPackaging/Manifest/Schema/2017/UapManifestSchema_v5.xsd") list(APPEND MANIFEST_UAP6 @@ -129,6 +137,25 @@ if ((XML_PARSER MATCHES msxml6) OR (XML_PARSER MATCHES xerces)) "http://schemas.microsoft.com/appx/manifest/uap/windows10/7" "uap7" "AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v7.xsd") list(APPEND MANIFEST_UAP8 "http://schemas.microsoft.com/appx/manifest/uap/windows10/8" "uap8" "AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v8.xsd") + list(APPEND MANIFEST_UAP10 + "http://schemas.microsoft.com/appx/manifest/uap/windows10/10" "uap10" "AppxPackaging/Manifest/Schema/2019/UapManifestSchema_v10.xsd" ) + list(APPEND MANIFEST_UAP11 + "http://schemas.microsoft.com/appx/manifest/uap/windows10/11" "uap11" "AppxPackaging/Manifest/Schema/2019/UapManifestSchema_v11.xsd" ) + list(APPEND MANIFEST_UAP12 + "http://schemas.microsoft.com/appx/manifest/uap/windows10/12" "uap12" "AppxPackaging/Manifest/Schema/2020/UapManifestSchema_v12.xsd" ) + list(APPEND MANIFEST_UAP13 + "http://schemas.microsoft.com/appx/manifest/uap/windows10/13" "uap13" "AppxPackaging/Manifest/Schema/2021/UapManifestSchema_v13.xsd" ) + list(APPEND MANIFEST_CLOUDFILES + "http://schemas.microsoft.com/appx/manifest/cloudfiles/windows10" "cloudfiles" "AppxPackaging/Manifest/Schema/2019/CloudFilesManifestSchema.xsd") + list(APPEND MANIFEST_PREVIEWAPPCOMPAT + "http://schemas.microsoft.com/appx/manifest/preview/windows10/msixappcompatsupport" "previewappcompat" "AppxPackaging/Manifest/Schema/2019/PreviewManifestSchema_MsixAppCompatSupport.xsd") + list(APPEND MANIFEST_PREVIEWAPPCOMPAT3 + "http://schemas.microsoft.com/appx/manifest/preview/windows10/msixappcompatsupport/3" "previewappcompat3" "AppxPackaging/Manifest/Schema/2020/PreviewManifestSchema_MsixAppCompatSupport_v3.xsd") + list(APPEND MANIFEST_DEPLOYMENT + "http://schemas.microsoft.com/appx/manifest/deployment/windows10" "deployment" "AppxPackaging/Manifest/Schema/2020/DeploymentManifestSchema.xsd") + list(APPEND MANIFEST_VIRTUALIZATION + "http://schemas.microsoft.com/appx/manifest/virtualization/windows10" "virtualization" "AppxPackaging/Manifest/Schema/2020/VirtualizationManifestSchema.xsd") + list(APPEND RESOURCES_APPXMANIFEST MANIFEST_FOUNDATION MANIFEST_UAP @@ -157,12 +184,25 @@ if ((XML_PARSER MATCHES msxml6) OR (XML_PARSER MATCHES xerces)) MANIFEST_DESK4 MANIFEST_DESK5 MANIFEST_DESK6 + MANIFEST_DESK7 + MANIFEST_DESK8 MANIFEST_COM MANIFEST_COM2 + MANIFEST_COM3 + MANIFEST_COM4 MANIFEST_UAP5 MANIFEST_UAP6 MANIFEST_UAP7 - MANIFEST_UAP8) + MANIFEST_UAP8 + MANIFEST_UAP10 + MANIFEST_UAP11 + MANIFEST_UAP12 + MANIFEST_UAP13 + MANIFEST_CLOUDFILES + MANIFEST_PREVIEWAPPCOMPAT + MANIFEST_PREVIEWAPPCOMPAT3 + MANIFEST_DEPLOYMENT + MANIFEST_VIRTUALIZATION) # Bundle manifest list(APPEND BUNDLE_2014 @@ -173,11 +213,14 @@ if ((XML_PARSER MATCHES msxml6) OR (XML_PARSER MATCHES xerces)) "http://schemas.microsoft.com/appx/2017/bundle" "b3" "AppxPackaging/Manifest/Schema/2017/BundleManifestSchema2017.xsd") list(APPEND BUNDLE_2018 "http://schemas.microsoft.com/appx/2018/bundle" "b4" "AppxPackaging/Manifest/Schema/2018/BundleManifestSchema2018.xsd") + list(APPEND BUNDLE_2019 + "http://schemas.microsoft.com/appx/2019/bundle" "b5" "AppxPackaging/Manifest/Schema/2019/BundleManifestSchema2019.xsd") list(APPEND RESOURCES_APPXBUNDLEMANIFEST BUNDLE_2014 BUNDLE_2016 BUNDLE_2017 - BUNDLE_2018) + BUNDLE_2018 + BUNDLE_2019) if (XML_PARSER MATCHES xerces) file(COPY ${RESOURCES_DIR} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") @@ -228,6 +271,13 @@ if ((XML_PARSER MATCHES msxml6) OR (XML_PARSER MATCHES xerces)) [[/]] APPTYPES_TEXT "${APPTYPES_TEXT}") + # Ilegal escaped characters + # ST_MutableDirectoryTarget + string(REGEX REPLACE + [[\\\$]] + [[$]] + APPTYPES_TEXT "${APPTYPES_TEXT}") + # Negative lookahead. I am not sure if we can do something here... maybe a semantic check? # ST_Parameters string(REGEX REPLACE @@ -236,6 +286,30 @@ if ((XML_PARSER MATCHES msxml6) OR (XML_PARSER MATCHES xerces)) APPTYPES_TEXT "${APPTYPES_TEXT}") file(WRITE "${RESOURCES_DIR}/${APPX_TYPES_FILE}" "${APPTYPES_TEXT}") + + # DesktopManifestSchema_v6.xsd + list(GET MANIFEST_DESK6 2 DESK6_FILE) + file(READ "${RESOURCES_DIR}/${DESK6_FILE}" DESK6_TEXT) + + # ST_ServiceName same as ST_Description above + string(REGEX REPLACE + [[\\x01-\\x1f]] + [[\\t\\n\\r]] + DESK6_TEXT "${DESK6_TEXT}") + + file(WRITE "${RESOURCES_DIR}/${DESK6_FILE}" "${DESK6_TEXT}") + + # DesktopManifestSchema_v7.xsd + list(GET MANIFEST_DESK7 2 DESK7_FILE) + file(READ "${RESOURCES_DIR}/${DESK7_FILE}" DESK7_TEXT) + + string(REGEX REPLACE + [[\\x01-\\x1f]] + [[\\t\\n\\r]] + DESK7_TEXT "${DESK7_TEXT}") + + file(WRITE "${RESOURCES_DIR}/${DESK7_FILE}" "${DESK7_TEXT}") + endif() function(CreateNamespaceManager LIST OUTPUT) diff --git a/lib/zlib/CMakeLists.txt b/lib/zlib/CMakeLists.txt index f631287e4..e2c6c9617 100644 --- a/lib/zlib/CMakeLists.txt +++ b/lib/zlib/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.4.4) +#cmake_minimum_required(VERSION 2.4.4) set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON) project(zlib C) diff --git a/makemac.sh b/makemac.sh index 9763ee7f6..8d0a635b0 100755 --- a/makemac.sh +++ b/makemac.sh @@ -9,6 +9,7 @@ validationParser=off pack=off samples=on tests=on +arch=x86_64 usage() { @@ -22,6 +23,7 @@ usage() echo $'\t' "--pack Include packaging features. Uses MSIX SDK Zlib and Xerces with validation parser on." echo $'\t' "--skip-samples Skip building samples." echo $'\t' "--skip-tests Skip building tests." + echo $'\t' "-arch arch Architecture. Default x86_64" } printsetup() @@ -68,6 +70,9 @@ while [ "$1" != "" ]; do -h ) usage exit ;; + -arch ) shift + arch=$1 + ;; * ) usage exit 1 esac @@ -83,7 +88,7 @@ find . -name *msix* -d | xargs rm -r echo "cmake -DCMAKE_BUILD_TYPE="$build $zlib "-DSKIP_BUNDLES="$bundle echo "-DXML_PARSER="$xmlparser "-DASAN="$addressSanitizer "-DUSE_VALIDATION_PARSER="$validationParser -echo "-DMSIX_PACK="$pack "-DMSIX_SAMPLES="$samples "-DMSIX_TESTS="$tests "-DMACOS=on .." +echo "-DMSIX_PACK="$pack "-DMSIX_SAMPLES="$samples "-DMSIX_TESTS="$tests "-DCMAKE_OSX_ARCHITECTURES="$arch "-DMACOS=on .." cmake -DCMAKE_BUILD_TYPE=$build \ -DXML_PARSER=$xmlparser \ -DSKIP_BUNDLES=$bundle \ @@ -92,5 +97,7 @@ cmake -DCMAKE_BUILD_TYPE=$build \ -DMSIX_PACK=$pack \ -DMSIX_SAMPLES=$samples \ -DMSIX_TESTS=$tests \ + -DCMAKE_OSX_ARCHITECTURES=$arch \ + -DCMAKE_TOOLCHAIN_FILE=../cmake/macos.cmake \ $zlib -DMACOS=on .. make diff --git a/pipelines/azure-pipelines-ios.yml b/pipelines/azure-pipelines-ios.yml index 2eced9c2b..a1e47f7b5 100644 --- a/pipelines/azure-pipelines-ios.yml +++ b/pipelines/azure-pipelines-ios.yml @@ -45,6 +45,9 @@ jobs: release_arm64: _arguments: -b MinSizeRel -arch arm64 _artifact: iOS-arm64 + release_arm64_nobundle: + _arguments: -b MinSizeRel -arch arm64 -sb + _artifact: iOS-arm64-nobundle steps: - task: Bash@3 displayName: Build diff --git a/pipelines/azure-pipelines-linux.yml b/pipelines/azure-pipelines-linux.yml index cec1558bd..2d838b26a 100644 --- a/pipelines/azure-pipelines-linux.yml +++ b/pipelines/azure-pipelines-linux.yml @@ -86,6 +86,8 @@ jobs: src/msix/AppxPackaging.hpp src/msix/MSIXWindows.hpp src/msix/MsixErrors.hpp + Package.nuspec + build/** TargetFolder: '$(Build.ArtifactStagingDirectory)' condition: succeededOrFailed() diff --git a/pipelines/azure-pipelines-macos.yml b/pipelines/azure-pipelines-macos.yml index 5620d075e..fbb28e519 100644 --- a/pipelines/azure-pipelines-macos.yml +++ b/pipelines/azure-pipelines-macos.yml @@ -29,7 +29,8 @@ pr: jobs: - job: macOS pool: - name: Hosted macOS + name: Azure Pipelines + vmImage: macOS-latest strategy: # TODO: add builds using xerces if needed. matrix: @@ -44,11 +45,33 @@ jobs: _artifact: MACOS-nobundle release_pack: _arguments: -b MinSizeRel --pack - _artifact: MACOSchk-pack + _artifact: MACOS-pack debug_pack: _arguments: -b Debug --pack _artifact: MACOSchk-pack + # arm64 + #debug_nopack_arm64: + # _arguments: -b Debug -arch arm64 --skip-tests + # _artifact: MACOSarm64chk + #release_nopack_arm64: + # _arguments: -b MinSizeRel -arch arm64 --skip-tests + # _artifact: MACOSarm64 + #release_nobundle_arm64: + # _arguments: -b MinSizeRel -sb -arch arm64 --skip-tests + # _artifact: MACOSarm64-nobundle + #release_pack_arm64: + # _arguments: -b MinSizeRel --pack -arch arm64 --skip-tests + # _artifact: MACOSarm64-pack + #debug_pack_arm64: + # _arguments: -b Debug --pack -arch arm64 --skip-tests + # _artifact: MACOSarm64chk-pack steps: + + # Az Pipelines has Xcode 11.6 as default. For arm64, change to supported Xcode. + - script: sudo xcode-select -switch /Applications/Xcode_12_beta.app/Contents/Developer + displayName: 'Set up' + condition: and(succeeded(), contains(variables['Agent.JobName'], 'arm64')) + - task: Bash@3 displayName: Build inputs: @@ -64,14 +87,14 @@ jobs: - script: 'msixtest/msixtest -s -r junit -o TEST-MsixSDK-$(_artifact).xml' workingDirectory: .vs displayName: 'macOS BVTs' - condition: and(succeeded(), contains(variables['Agent.JobName'], 'release')) + condition: and(succeeded(), contains(variables['Agent.JobName'], 'release'), not(contains(variables['Agent.JobName'], 'arm64'))) - task: PublishTestResults@2 displayName: 'Publish $(_artifact) Test Results' inputs: failTaskOnFailedTests: true testRunTitle: $(_artifact) - condition: and(succeededOrFailed(), contains(variables['Agent.JobName'], 'release')) + condition: and(succeededOrFailed(), contains(variables['Agent.JobName'], 'release'), not(contains(variables['Agent.JobName'], 'arm64'))) - task: CopyFiles@2 displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)' @@ -95,3 +118,42 @@ jobs: inputs: ArtifactName: $(_artifact) condition: succeededOrFailed() + +#- job: macOS_universal_nopack +# dependsOn: +# - 'macOS' +# pool: +# name: Azure Pipelines +# vmImage: macOS-latest +# steps: +# - template: templates/macos-universal.yml +# parameters: +# artifact_output: MACOS-Universal +# artifact_x86: MACOS +# artifact_arm64: MACOSarm64 + +#- job: macOS_universal_nobundle +# dependsOn: +# - 'macOS' +# pool: +# name: Azure Pipelines +# vmImage: macOS-latest +# steps: +# - template: templates/macos-universal.yml +# parameters: +# artifact_output: MACOS-nobundle-Universal +# artifact_x86: MACOS-nobundle +# artifact_arm64: MACOSarm64-nobundle + +#- job: macOS_universal_pack +# dependsOn: +# - 'macOS' +# pool: +# name: Azure Pipelines +# vmImage: macOS-latest +# steps: +# - template: templates/macos-universal.yml +# parameters: +# artifact_output: MACOS-pack-Universal +# artifact_x86: MACOS-pack +# artifact_arm64: MACOSarm64-pack diff --git a/pipelines/azure-pipelines-windows.yml b/pipelines/azure-pipelines-windows.yml index 2c7a469ab..7a541afdb 100644 --- a/pipelines/azure-pipelines-windows.yml +++ b/pipelines/azure-pipelines-windows.yml @@ -28,7 +28,7 @@ pr: jobs: - job: Windows pool: - name: Hosted VS2017 + vmImage: 'windows-latest' demands: Cmd strategy: # TODO: add debug for validation parser and xerces if needed. diff --git a/pipelines/templates/macos-universal.yml b/pipelines/templates/macos-universal.yml new file mode 100644 index 000000000..428a6e2b4 --- /dev/null +++ b/pipelines/templates/macos-universal.yml @@ -0,0 +1,35 @@ +# Template helper to package projects (using DotNetCoreCLI Publish Command) and publish them as an artifact +parameters: + artifact_output: '' + artifact_x86: '' + artifact_arm64: '' + +steps: +- task: DownloadBuildArtifacts@0 + displayName: 'Download ${{ parameters.artifact_x86 }}' + inputs: + artifactName: ${{ parameters.artifact_x86 }} + +- task: DownloadBuildArtifacts@0 + displayName: 'Download ${{ parameters.artifact_arm64 }}' + inputs: + artifactName: ${{ parameters.artifact_arm64 }} + +- script: | + sudo xcode-select -switch /Applications/Xcode_12_beta.app/Contents/Developer + + cp -R $(System.ArtifactsDirectory)/${{ parameters.artifact_x86 }} $(System.ArtifactsDirectory)/Universal + rm $(System.ArtifactsDirectory)/Universal/lib/libmsix.dylib + rm $(System.ArtifactsDirectory)/Universal/bin/makemsix + + lipo -create -output $(System.ArtifactsDirectory)/Universal/lib/libmsix.dylib $(System.ArtifactsDirectory)/${{ parameters.artifact_x86 }}/lib/libmsix.dylib $(System.ArtifactsDirectory)/${{ parameters.artifact_arm64 }}/lib/libmsix.dylib + + lipo -create -output $(System.ArtifactsDirectory)/Universal/bin/makemsix $(System.ArtifactsDirectory)/${{ parameters.artifact_x86 }}/bin/makemsix $(System.ArtifactsDirectory)/${{ parameters.artifact_arm64 }}/bin/makemsix + + displayName: 'Command Line Script' + +- task: PublishBuildArtifacts@1 + displayName: 'Publish Universal' + inputs: + PathtoPublish: '$(System.ArtifactsDirectory)/Universal' + ArtifactName: ${{ parameters.artifact_output }} diff --git a/resources/AppxPackaging/Manifest/Schema/2015/AppxManifestTypes.xsd b/resources/AppxPackaging/Manifest/Schema/2015/AppxManifestTypes.xsd index c339b7d37..10790d4af 100644 --- a/resources/AppxPackaging/Manifest/Schema/2015/AppxManifestTypes.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2015/AppxManifestTypes.xsd @@ -103,6 +103,19 @@ + + + + + + + + + + + + + @@ -111,6 +124,19 @@ + + + + + + + + + + + + + @@ -129,6 +155,12 @@ + + + + + + @@ -141,6 +173,12 @@ + + + + + + @@ -424,6 +462,13 @@ + + + + + + + @@ -749,6 +794,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -756,6 +831,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -800,7 +896,7 @@ - + @@ -810,6 +906,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1128,6 +1278,13 @@ + + + + + + + @@ -1231,6 +1388,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1322,5 +1594,14 @@ - + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2015/BundleManifestSchema2013.xsd b/resources/AppxPackaging/Manifest/Schema/2015/BundleManifestSchema2013.xsd index eb988ea6f..f90a145c8 100644 --- a/resources/AppxPackaging/Manifest/Schema/2015/BundleManifestSchema2013.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2015/BundleManifestSchema2013.xsd @@ -5,10 +5,12 @@ xmlns="http://schemas.microsoft.com/appx/2013/bundle" xmlns:b="http://schemas.microsoft.com/appx/2013/bundle" xmlns:t="http://schemas.microsoft.com/appx/manifest/types" - xmlns:b4="http://schemas.microsoft.com/appx/2018/bundle"> + xmlns:b4="http://schemas.microsoft.com/appx/2018/bundle" + xmlns:b5="http://schemas.microsoft.com/appx/2019/bundle"> + @@ -23,8 +25,8 @@ - - + + @@ -51,7 +53,8 @@ - + + diff --git a/resources/AppxPackaging/Manifest/Schema/2015/BundleManifestSchema2014.xsd b/resources/AppxPackaging/Manifest/Schema/2015/BundleManifestSchema2014.xsd index 688c81c1a..d31800aaa 100644 --- a/resources/AppxPackaging/Manifest/Schema/2015/BundleManifestSchema2014.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2015/BundleManifestSchema2014.xsd @@ -5,10 +5,12 @@ xmlns="http://schemas.microsoft.com/appx/2013/bundle" xmlns:b="http://schemas.microsoft.com/appx/2013/bundle" xmlns:t="http://schemas.microsoft.com/appx/manifest/types" - xmlns:b4="http://schemas.microsoft.com/appx/2018/bundle"> + xmlns:b4="http://schemas.microsoft.com/appx/2018/bundle" + xmlns:b5="http://schemas.microsoft.com/appx/2019/bundle"> + @@ -23,8 +25,8 @@ - - + + @@ -51,7 +53,8 @@ - + + diff --git a/resources/AppxPackaging/Manifest/Schema/2015/ComManifestSchema.xsd b/resources/AppxPackaging/Manifest/Schema/2015/ComManifestSchema.xsd index 1514057ce..fff0a032d 100644 --- a/resources/AppxPackaging/Manifest/Schema/2015/ComManifestSchema.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2015/ComManifestSchema.xsd @@ -21,11 +21,17 @@ xmlns:f="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" xmlns:com2="http://schemas.microsoft.com/appx/manifest/com/windows10/2" + xmlns:com3="http://schemas.microsoft.com/appx/manifest/com/windows10/3" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" > + + + @@ -48,7 +54,7 @@ - + @@ -60,7 +66,7 @@ - + @@ -69,17 +75,51 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -116,6 +156,8 @@ + + @@ -125,6 +167,11 @@ + + + + + @@ -166,7 +213,7 @@ - + @@ -269,7 +316,7 @@ - + @@ -277,27 +324,27 @@ - + - - - - + + + + - + - - + + @@ -307,10 +354,10 @@ - + - - + + @@ -329,16 +376,16 @@ - + - + - + @@ -367,7 +414,7 @@ - + @@ -382,9 +429,9 @@ - - - + + + @@ -409,103 +456,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/AppxPackaging/Manifest/Schema/2015/DesktopManifestSchema.xsd b/resources/AppxPackaging/Manifest/Schema/2015/DesktopManifestSchema.xsd index b6737ab40..8ed9985a2 100644 --- a/resources/AppxPackaging/Manifest/Schema/2015/DesktopManifestSchema.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2015/DesktopManifestSchema.xsd @@ -24,6 +24,8 @@ xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap8="http://schemas.microsoft.com/appx/manifest/uap/windows10/8" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" xmlns:rescap5="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/5" > @@ -31,6 +33,8 @@ + + @@ -43,6 +47,8 @@ + + diff --git a/resources/AppxPackaging/Manifest/Schema/2015/FoundationManifestSchema.xsd b/resources/AppxPackaging/Manifest/Schema/2015/FoundationManifestSchema.xsd index 53b27bad6..c978ccdc5 100644 --- a/resources/AppxPackaging/Manifest/Schema/2015/FoundationManifestSchema.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2015/FoundationManifestSchema.xsd @@ -1,4 +1,4 @@ - + @@ -50,6 +54,8 @@ + + diff --git a/resources/AppxPackaging/Manifest/Schema/2015/UapManifestSchema.xsd b/resources/AppxPackaging/Manifest/Schema/2015/UapManifestSchema.xsd index 087d9aa79..c77711acb 100644 --- a/resources/AppxPackaging/Manifest/Schema/2015/UapManifestSchema.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2015/UapManifestSchema.xsd @@ -1,4 +1,4 @@ - + @@ -33,6 +37,8 @@ + + diff --git a/resources/AppxPackaging/Manifest/Schema/2017/BundleManifestSchema2017.xsd b/resources/AppxPackaging/Manifest/Schema/2017/BundleManifestSchema2017.xsd index fe00c7d29..05e714cf0 100644 --- a/resources/AppxPackaging/Manifest/Schema/2017/BundleManifestSchema2017.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2017/BundleManifestSchema2017.xsd @@ -5,10 +5,12 @@ xmlns="http://schemas.microsoft.com/appx/2017/bundle" xmlns:b="http://schemas.microsoft.com/appx/2017/bundle" xmlns:t="http://schemas.microsoft.com/appx/manifest/types" - xmlns:b4="http://schemas.microsoft.com/appx/2018/bundle"> + xmlns:b4="http://schemas.microsoft.com/appx/2018/bundle" + xmlns:b5="http://schemas.microsoft.com/appx/2019/bundle"> + @@ -22,16 +24,18 @@ - - - + + + + - - - + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2017/ComManifestSchema_v2.xsd b/resources/AppxPackaging/Manifest/Schema/2017/ComManifestSchema_v2.xsd index 497225f6c..02e8da5f6 100644 --- a/resources/AppxPackaging/Manifest/Schema/2017/ComManifestSchema_v2.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2017/ComManifestSchema_v2.xsd @@ -20,11 +20,17 @@ xmlns:t="http://schemas.microsoft.com/appx/manifest/types" xmlns:f="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" + xmlns:com3="http://schemas.microsoft.com/appx/manifest/com/windows10/3" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" > + + + @@ -71,6 +77,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -107,6 +147,8 @@ + + @@ -116,6 +158,11 @@ + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2017/DesktopManifestSchema_v3.xsd b/resources/AppxPackaging/Manifest/Schema/2017/DesktopManifestSchema_v3.xsd index 528b81576..4eded207d 100644 --- a/resources/AppxPackaging/Manifest/Schema/2017/DesktopManifestSchema_v3.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2017/DesktopManifestSchema_v3.xsd @@ -23,11 +23,15 @@ xmlns:f="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:desktop3="http://schemas.microsoft.com/appx/manifest/desktop/windows10/3" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" > + + @@ -37,6 +41,8 @@ + + @@ -61,21 +67,13 @@ - + - - - - - - - - diff --git a/resources/AppxPackaging/Manifest/Schema/2017/DesktopManifestSchema_v4.xsd b/resources/AppxPackaging/Manifest/Schema/2017/DesktopManifestSchema_v4.xsd index 88ebb381a..564c4e4a4 100644 --- a/resources/AppxPackaging/Manifest/Schema/2017/DesktopManifestSchema_v4.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2017/DesktopManifestSchema_v4.xsd @@ -23,11 +23,15 @@ xmlns:f="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" > + + @@ -36,6 +40,8 @@ + + diff --git a/resources/AppxPackaging/Manifest/Schema/2017/RestrictedCapabilitiesManifestSchema_v4.xsd b/resources/AppxPackaging/Manifest/Schema/2017/RestrictedCapabilitiesManifestSchema_v4.xsd index ad7e05c44..8af44a639 100644 --- a/resources/AppxPackaging/Manifest/Schema/2017/RestrictedCapabilitiesManifestSchema_v4.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2017/RestrictedCapabilitiesManifestSchema_v4.xsd @@ -21,10 +21,14 @@ xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/4" xmlns:t="http://schemas.microsoft.com/appx/manifest/types" xmlns:f="http://schemas.microsoft.com/appx/manifest/foundation/windows10" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" > + + @@ -34,6 +38,8 @@ + + diff --git a/resources/AppxPackaging/Manifest/Schema/2017/UapManifestSchema_v5.xsd b/resources/AppxPackaging/Manifest/Schema/2017/UapManifestSchema_v5.xsd index 95cde37c1..c934cbe25 100644 --- a/resources/AppxPackaging/Manifest/Schema/2017/UapManifestSchema_v5.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2017/UapManifestSchema_v5.xsd @@ -23,6 +23,8 @@ xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:uap6="http://schemas.microsoft.com/appx/manifest/uap/windows10/6" xmlns:uap8="http://schemas.microsoft.com/appx/manifest/uap/windows10/8" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" xmlns:wincap3="http://schemas.microsoft.com/appx/manifest/foundation/windows10/windowscapabilities/3" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" xmlns:iot2="http://schemas.microsoft.com/appx/manifest/iot/windows10/2" @@ -33,6 +35,8 @@ + + @@ -49,6 +53,8 @@ + + @@ -76,6 +82,7 @@ + @@ -172,6 +179,7 @@ + @@ -186,6 +194,7 @@ + diff --git a/resources/AppxPackaging/Manifest/Schema/2017/UapManifestSchema_v6.xsd b/resources/AppxPackaging/Manifest/Schema/2017/UapManifestSchema_v6.xsd index a46325dad..3624a62f7 100644 --- a/resources/AppxPackaging/Manifest/Schema/2017/UapManifestSchema_v6.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2017/UapManifestSchema_v6.xsd @@ -19,10 +19,14 @@ xmlns="http://schemas.microsoft.com/appx/manifest/uap/windows10/6" xmlns:t="http://schemas.microsoft.com/appx/manifest/types" xmlns:f="http://schemas.microsoft.com/appx/manifest/foundation/windows10" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" > + + @@ -35,6 +39,8 @@ + + diff --git a/resources/AppxPackaging/Manifest/Schema/2018/BundleManifestSchema2018.xsd b/resources/AppxPackaging/Manifest/Schema/2018/BundleManifestSchema2018.xsd index a0fd1d9ad..392c2acd7 100644 --- a/resources/AppxPackaging/Manifest/Schema/2018/BundleManifestSchema2018.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2018/BundleManifestSchema2018.xsd @@ -4,9 +4,11 @@ targetNamespace="http://schemas.microsoft.com/appx/2018/bundle" xmlns="http://schemas.microsoft.com/appx/2018/bundle" xmlns:b="http://schemas.microsoft.com/appx/2018/bundle" - xmlns:t="http://schemas.microsoft.com/appx/manifest/types"> + xmlns:t="http://schemas.microsoft.com/appx/manifest/types" + xmlns:b5="http://schemas.microsoft.com/appx/2019/bundle"> + @@ -20,16 +22,18 @@ - - - + + + + - - - + + + + @@ -46,7 +50,7 @@ - + diff --git a/resources/AppxPackaging/Manifest/Schema/2018/DesktopManifestSchema_v6.xsd b/resources/AppxPackaging/Manifest/Schema/2018/DesktopManifestSchema_v6.xsd index 0b87d6625..3a94ba968 100644 --- a/resources/AppxPackaging/Manifest/Schema/2018/DesktopManifestSchema_v6.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2018/DesktopManifestSchema_v6.xsd @@ -21,10 +21,16 @@ xmlns="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6" xmlns:t="http://schemas.microsoft.com/appx/manifest/types" xmlns:f="http://schemas.microsoft.com/appx/manifest/foundation/windows10" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" + xmlns:desktop8="http://schemas.microsoft.com/appx/manifest/desktop/windows10/8" > + + + @@ -35,6 +41,8 @@ + + @@ -43,20 +51,27 @@ - + + + + + + + + - + - + @@ -171,6 +186,7 @@ + @@ -179,4 +195,4 @@ - \ No newline at end of file + diff --git a/resources/AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v7.xsd b/resources/AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v7.xsd index be01c7e8f..d819aaff2 100644 --- a/resources/AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v7.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v7.xsd @@ -21,12 +21,16 @@ xmlns:f="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap8="http://schemas.microsoft.com/appx/manifest/uap/windows10/8" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" > + + @@ -36,6 +40,8 @@ + + @@ -64,6 +70,7 @@ + diff --git a/resources/AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v8.xsd b/resources/AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v8.xsd index a5398c813..ba210db9f 100644 --- a/resources/AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v8.xsd +++ b/resources/AppxPackaging/Manifest/Schema/2018/UapManifestSchema_v8.xsd @@ -20,11 +20,15 @@ xmlns:t="http://schemas.microsoft.com/appx/manifest/types" xmlns:f="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" + xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" + xmlns:uap11="http://schemas.microsoft.com/appx/manifest/uap/windows10/11" > + + @@ -34,6 +38,8 @@ + + diff --git a/resources/AppxPackaging/Manifest/Schema/2019/BundleManifestSchema2019.xsd b/resources/AppxPackaging/Manifest/Schema/2019/BundleManifestSchema2019.xsd new file mode 100644 index 000000000..db5c56ea3 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2019/BundleManifestSchema2019.xsd @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2019/CloudFilesManifestSchema.xsd b/resources/AppxPackaging/Manifest/Schema/2019/CloudFilesManifestSchema.xsd new file mode 100644 index 000000000..2f03ca9c1 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2019/CloudFilesManifestSchema.xsd @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/AppxPackaging/Manifest/Schema/2019/ComManifestSchema_v3.xsd b/resources/AppxPackaging/Manifest/Schema/2019/ComManifestSchema_v3.xsd new file mode 100644 index 000000000..f5eec7ce4 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2019/ComManifestSchema_v3.xsd @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2019/PreviewManifestSchema_MsixAppCompatSupport.xsd b/resources/AppxPackaging/Manifest/Schema/2019/PreviewManifestSchema_MsixAppCompatSupport.xsd new file mode 100644 index 000000000..0bdfefcf7 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2019/PreviewManifestSchema_MsixAppCompatSupport.xsd @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2019/UapManifestSchema_v10.xsd b/resources/AppxPackaging/Manifest/Schema/2019/UapManifestSchema_v10.xsd new file mode 100644 index 000000000..baa7a0ca1 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2019/UapManifestSchema_v10.xsd @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2019/UapManifestSchema_v11.xsd b/resources/AppxPackaging/Manifest/Schema/2019/UapManifestSchema_v11.xsd new file mode 100644 index 000000000..170c7065c --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2019/UapManifestSchema_v11.xsd @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2020/ComManifestSchema_v4.xsd b/resources/AppxPackaging/Manifest/Schema/2020/ComManifestSchema_v4.xsd new file mode 100644 index 000000000..1b5fd72b0 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2020/ComManifestSchema_v4.xsd @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2020/DeploymentManifestSchema.xsd b/resources/AppxPackaging/Manifest/Schema/2020/DeploymentManifestSchema.xsd new file mode 100644 index 000000000..5cc28fb2e --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2020/DeploymentManifestSchema.xsd @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2020/DesktopManifestSchema_v7.xsd b/resources/AppxPackaging/Manifest/Schema/2020/DesktopManifestSchema_v7.xsd new file mode 100644 index 000000000..48a56ecfb --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2020/DesktopManifestSchema_v7.xsd @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2020/PreviewManifestSchema_MsixAppCompatSupport_v3.xsd b/resources/AppxPackaging/Manifest/Schema/2020/PreviewManifestSchema_MsixAppCompatSupport_v3.xsd new file mode 100644 index 000000000..0d5dd6d69 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2020/PreviewManifestSchema_MsixAppCompatSupport_v3.xsd @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2020/UapManifestSchema_v12.xsd b/resources/AppxPackaging/Manifest/Schema/2020/UapManifestSchema_v12.xsd new file mode 100644 index 000000000..228aab3f3 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2020/UapManifestSchema_v12.xsd @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2020/VirtualizationManifestSchema.xsd b/resources/AppxPackaging/Manifest/Schema/2020/VirtualizationManifestSchema.xsd new file mode 100644 index 000000000..8567e8c06 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2020/VirtualizationManifestSchema.xsd @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2021/DesktopManifestSchema_v8.xsd b/resources/AppxPackaging/Manifest/Schema/2021/DesktopManifestSchema_v8.xsd new file mode 100644 index 000000000..a67b1a04a --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2021/DesktopManifestSchema_v8.xsd @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/AppxPackaging/Manifest/Schema/2021/UapManifestSchema_v13.xsd b/resources/AppxPackaging/Manifest/Schema/2021/UapManifestSchema_v13.xsd new file mode 100644 index 000000000..9cf66ddb0 --- /dev/null +++ b/resources/AppxPackaging/Manifest/Schema/2021/UapManifestSchema_v13.xsd @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/BundleSample/BundleSample.cpp b/sample/BundleSample/BundleSample.cpp index dc1457a90..f768fbdf9 100644 --- a/sample/BundleSample/BundleSample.cpp +++ b/sample/BundleSample/BundleSample.cpp @@ -201,7 +201,7 @@ int main(int argc, char* argv[]) { std::cout << "Error: " << std::hex << hr << " while extracting the appx package" < text; - auto logResult = GetLogTextUTF8(MyAllocate, &text); + auto logResult = MsixGetLogTextUTF8(MyAllocate, &text); if (0 == logResult) { std::cout << "LOG:" << std::endl << text.content << std::endl; } diff --git a/sample/ExtractContentsSample/ExtractContentsSample.cpp b/sample/ExtractContentsSample/ExtractContentsSample.cpp index e8442eafd..3415206cf 100644 --- a/sample/ExtractContentsSample/ExtractContentsSample.cpp +++ b/sample/ExtractContentsSample/ExtractContentsSample.cpp @@ -478,7 +478,7 @@ int main(int argc, char* argv[]) { std::cout << "Error: " << std::hex << result << " while extracting the appx package" < text; - auto logResult = GetLogTextUTF8(MyAllocate, &text); + auto logResult = MsixGetLogTextUTF8(MyAllocate, &text); if (0 == logResult) { std::cout << "LOG:" << std::endl << text.content << std::endl; } diff --git a/sample/OverrideLanguageSample/OverrideLanguageSample.cpp b/sample/OverrideLanguageSample/OverrideLanguageSample.cpp index af5b9e6e4..3fa181447 100644 --- a/sample/OverrideLanguageSample/OverrideLanguageSample.cpp +++ b/sample/OverrideLanguageSample/OverrideLanguageSample.cpp @@ -233,7 +233,7 @@ int main(int argc, char* argv[]) { std::cout << "Error: " << std::hex << hr << " while reading the bundle" << std::endl; Text text; - auto logResult = GetLogTextUTF8(MyAllocate, &text); + auto logResult = MsixGetLogTextUTF8(MyAllocate, &text); if (0 == logResult) { std::cout << "LOG:" << std::endl << text.content << std::endl; diff --git a/sample/OverrideStreamSample/OverrideStreamSample.cpp b/sample/OverrideStreamSample/OverrideStreamSample.cpp index 8bed4ed51..616bd38f2 100644 --- a/sample/OverrideStreamSample/OverrideStreamSample.cpp +++ b/sample/OverrideStreamSample/OverrideStreamSample.cpp @@ -446,7 +446,7 @@ int main(int argc, char* argv[]) { std::cout << "Error: " << std::hex << hr << " while extracting the appx package" < text; - auto logResult = GetLogTextUTF8(MyAllocate, &text); + auto logResult = MsixGetLogTextUTF8(MyAllocate, &text); if (0 == logResult) { std::cout << "LOG:" << std::endl << text.content << std::endl; } diff --git a/sample/PackSample/PackSample.cpp b/sample/PackSample/PackSample.cpp index 6ecf00c43..cee3d6762 100644 --- a/sample/PackSample/PackSample.cpp +++ b/sample/PackSample/PackSample.cpp @@ -329,7 +329,7 @@ int main(int argc, char* argv[]) remove(package.c_str()); std::cout << "Error: " << std::hex << hr << " while extracting the appx package" < text; - auto logResult = GetLogTextUTF8(MyAllocate, &text); + auto logResult = MsixGetLogTextUTF8(MyAllocate, &text); if (0 == logResult) { std::cout << "LOG:" << std::endl << text.content << std::endl; diff --git a/src/inc/internal/AppxBlockMapWriter.hpp b/src/inc/internal/AppxBlockMapWriter.hpp index 0ae4782a9..1fa78ce74 100644 --- a/src/inc/internal/AppxBlockMapWriter.hpp +++ b/src/inc/internal/AppxBlockMapWriter.hpp @@ -4,6 +4,7 @@ // #pragma once +#include "Crypto.hpp" #include "XmlWriter.hpp" #include "ComHelper.hpp" @@ -18,6 +19,7 @@ namespace MSIX { public: BlockMapWriter(); + void EnableFileHash(); void AddFile(const std::string& name, std::uint64_t uncompressedSize, std::uint32_t lfh); void AddBlock(const std::vector& block, ULONG size, bool isCompressed); void CloseFile(); @@ -26,5 +28,10 @@ namespace MSIX { protected: XmlWriter m_xmlWriter; + + private: + MSIX::SHA256 m_fileHashEngine; + bool m_enableFileHash = false; + bool m_addFileHash = false; }; } \ No newline at end of file diff --git a/src/inc/internal/AppxBundleWriter.hpp b/src/inc/internal/AppxBundleWriter.hpp new file mode 100644 index 000000000..82fa589fa --- /dev/null +++ b/src/inc/internal/AppxBundleWriter.hpp @@ -0,0 +1,86 @@ +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#pragma once + +#include "AppxPackaging.hpp" +#include "ComHelper.hpp" +#include "DirectoryObject.hpp" +#include "AppxBlockMapWriter.hpp" +#include "ContentTypeWriter.hpp" +#include "ZipObjectWriter.hpp" +#include "BundleWriterHelper.hpp" +#include "BundleManifestWriter.hpp" +#include "AppxPackageInfo.hpp" + +// internal interface +// {ca90bcd9-78a2-4773-820c-0b687de49f99} +#ifndef WIN32 +interface IBundleWriter : public IUnknown +#else +#include "Unknwn.h" +#include "Objidl.h" +class IBundleWriter : public IUnknown +#endif +{ +public: + virtual void ProcessBundlePayload(const MSIX::ComPtr& from, bool flatBundle) = 0; + virtual void ProcessBundlePayloadFromMappingFile(std::map fileList, bool flatBundle) = 0; + virtual void ProcessExternalPackages(std::map externalPackagesList) = 0; +}; +MSIX_INTERFACE(IBundleWriter, 0xca90bcd9,0x78a2,0x4773,0x82,0x0c,0x0b,0x68,0x7d,0xe4,0x9f,0x99); + +namespace MSIX { + class AppxBundleWriter final : public ComClass + { + public: + AppxBundleWriter(IMsixFactory* factory, const ComPtr& zip, std::uint64_t bundleVersion); + ~AppxBundleWriter() {}; + + // IBundleWriter + void ProcessBundlePayload(const ComPtr& from, bool flatBundle) override; + void ProcessBundlePayloadFromMappingFile(std::map fileList, bool flatBundle) override; + void ProcessExternalPackages(std::map externalPackagesList) override; + + // IAppxBundleWriter + HRESULT STDMETHODCALLTYPE AddPayloadPackage(LPCWSTR fileName, IStream* packageStream) noexcept override; + HRESULT STDMETHODCALLTYPE Close() noexcept override; + + // IAppxBundleWriter4 + HRESULT STDMETHODCALLTYPE AddPackageReference(LPCWSTR fileName, IStream* inputStream, + BOOL isDefaultApplicablePackage) noexcept override; + HRESULT STDMETHODCALLTYPE AddPayloadPackage(LPCWSTR fileName, IStream* packageStream, + BOOL isDefaultApplicablePackage) noexcept override; + HRESULT STDMETHODCALLTYPE AddExternalPackageReference(LPCWSTR fileName, IStream* inputStream, + BOOL isDefaultApplicablePackage) noexcept override; + + protected: + typedef enum + { + Open = 1, + Closed = 2, + Failed = 3 + } + WriterState; + + void AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, + bool addToBlockMap, const char* contentType, bool forceContentTypeOverride = false); + + void AddPackageReferenceInternal(std::string fileName, IStream* packageStream, bool isDefaultApplicablePackage); + + void AddExternalPackageReferenceInternal(std::string fileName, IStream* packageStream, bool isDefaultApplicablePackage); + + void ValidateAndAddPayloadFile(const std::string& name, IStream* stream, APPX_COMPRESSION_OPTION compressionOpt, const char* contentType); + + void ValidateCompressionOption(APPX_COMPRESSION_OPTION compressionOpt); + + WriterState m_state; + ComPtr m_factory; + ComPtr m_zipWriter; + BlockMapWriter m_blockMapWriter; + ContentTypeWriter m_contentTypeWriter; + BundleWriterHelper m_bundleWriterHelper; + }; +} + diff --git a/src/inc/internal/AppxFactory.hpp b/src/inc/internal/AppxFactory.hpp index 3d6efc372..4725c96dd 100644 --- a/src/inc/internal/AppxFactory.hpp +++ b/src/inc/internal/AppxFactory.hpp @@ -42,8 +42,9 @@ namespace MSIX { class AppxFactory final : public ComClass { public: - AppxFactory(MSIX_VALIDATION_OPTION validationOptions, MSIX_APPLICABILITY_OPTIONS applicability, COTASKMEMALLOC* memalloc, COTASKMEMFREE* memfree ) : - m_validationOptions(validationOptions), m_applicabilityFlags(applicability), m_memalloc(memalloc), m_memfree(memfree) + AppxFactory(MSIX_VALIDATION_OPTION validationOptions, MSIX_APPLICABILITY_OPTIONS applicability, MSIX_FACTORY_OPTIONS factoryOptions, + COTASKMEMALLOC* memalloc, COTASKMEMFREE* memfree ) : + m_validationOptions(validationOptions), m_applicabilityFlags(applicability), m_factoryOptions(factoryOptions), m_memalloc(memalloc), m_memfree(memfree) { ThrowErrorIf(Error::InvalidParameter, (m_memalloc == nullptr || m_memfree == nullptr), "allocator/deallocator pair not specified.") ComPtr self; @@ -90,6 +91,7 @@ namespace MSIX { COTASKMEMALLOC* m_memalloc; COTASKMEMFREE* m_memfree; MSIX_VALIDATION_OPTION m_validationOptions; + MSIX_FACTORY_OPTIONS m_factoryOptions; ComPtr m_resourcezip; std::vector m_resourcesVector; MSIX_APPLICABILITY_OPTIONS m_applicabilityFlags; diff --git a/src/inc/internal/AppxManifestObject.hpp b/src/inc/internal/AppxManifestObject.hpp index fc3c6d0ce..b02061d4f 100644 --- a/src/inc/internal/AppxManifestObject.hpp +++ b/src/inc/internal/AppxManifestObject.hpp @@ -32,9 +32,53 @@ class IAppxManifestObject : public IUnknown }; MSIX_INTERFACE(IAppxManifestObject, 0xeff6d561,0xa236,0x4058,0x9f,0x1d,0x8f,0x93,0x63,0x3f,0xba,0x4b); +// {daf72e2b-6252-4ed3-a476-5fb656aa0e2c} +#ifndef WIN32 +interface IAppxManifestTargetDeviceFamilyInternal : public IUnknown +#else +#include "Unknwn.h" +#include "Objidl.h" +class IAppxManifestTargetDeviceFamilyInternal : public IUnknown +#endif +{ +public: + virtual const std::string& GetName() = 0; +}; +MSIX_INTERFACE(IAppxManifestTargetDeviceFamilyInternal, 0xdaf72e2b,0x6252,0x4ed3,0xa4,0x76,0x5f,0xb6,0x56,0xaa,0x0e,0x2c); + +// {9e2fb304-cec6-4ef0-8df3-10bb2ce714a3} +#ifndef WIN32 +interface IAppxManifestQualifiedResourceInternal : public IUnknown +#else +#include "Unknwn.h" +#include "Objidl.h" +class IAppxManifestQualifiedResourceInternal : public IUnknown +#endif +{ +public: + virtual const std::string& GetLanguage() = 0; +}; +MSIX_INTERFACE(IAppxManifestQualifiedResourceInternal, 0x9e2fb304,0xcec6,0x4ef0,0x8d,0xf3,0x10,0xbb,0x2c,0xe7,0x14,0xa3); + +// {6c37be69-b1e0-4764-b892-12a3c5e094a4} +#ifndef WIN32 +interface IAppxManifestMainPackageDependencyInternal : public IUnknown +#else +#include "Unknwn.h" +#include "Objidl.h" +class IAppxManifestMainPackageDependencyInternal : public IUnknown +#endif +{ +public: + virtual const std::string& GetName() = 0; + virtual const std::string& GetPublisher() = 0; + virtual const std::string& GetPackageFamilyName() = 0; +}; +MSIX_INTERFACE(IAppxManifestMainPackageDependencyInternal, 0x6c37be69,0xb1e0,0x4764,0xb8,0x92,0x12,0xa3,0xc5,0xe0,0x94,0xa4); + namespace MSIX { - class AppxManifestTargetDeviceFamily final : public ComClass + class AppxManifestTargetDeviceFamily final : public ComClass { public: AppxManifestTargetDeviceFamily(IMsixFactory* factory, std::string& name, std::string& minVersion, std::string& maxVersion) : @@ -70,6 +114,12 @@ namespace MSIX { return m_factory->MarshalOutStringUtf8(m_name, name); } CATCH_RETURN(); + //IAppxManifestTargetDeviceFamilyInternal + const std::string& GetName() override + { + return m_name; + } + protected: ComPtr m_factory; std::string m_name; @@ -247,7 +297,7 @@ namespace MSIX { std::string m_mainPackageName; }; - class AppxManifestMainPackageDependency final : public ComClass + class AppxManifestMainPackageDependency final : public ComClass { public: AppxManifestMainPackageDependency(IMsixFactory* factory, const std::string& name, const std::string& publisher, const std::string& packageFamilyName) : @@ -292,6 +342,22 @@ namespace MSIX { return m_factory->MarshalOutStringUtf8(m_packageFamilyName, packageFamilyName); } CATCH_RETURN(); + //IAppxManifestMainPackageDependencyInternal + const std::string& GetName() override + { + return m_name; + } + + const std::string& GetPublisher() override + { + return m_publisher; + } + + const std::string& GetPackageFamilyName() override + { + return m_packageFamilyName; + } + protected: ComPtr m_factory; std::string m_name; @@ -299,6 +365,51 @@ namespace MSIX { std::string m_packageFamilyName; }; + class AppxManifestQualifiedResource final : public ComClass + { + public: + AppxManifestQualifiedResource(IMsixFactory* factory, std::string& language, std::string& scale, std::string& DXFeatureLevel) : + m_factory(factory), m_language(language) + { + //TODO: Process and assign scale and DXFeatureLevel + } + + // IAppxManifestQualifiedResource + HRESULT STDMETHODCALLTYPE GetLanguage(LPWSTR *language) noexcept override try + { + ThrowErrorIf(Error::InvalidParameter, (language == nullptr || *language != nullptr), "bad pointer"); + return m_factory->MarshalOutString(m_language, language); + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE GetScale(UINT32 *scale) noexcept override try + { + return static_cast(Error::NotImplemented); + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE GetDXFeatureLevel(DX_FEATURE_LEVEL *dxFeatureLevel) noexcept override try + { + return static_cast(Error::NotImplemented); + } CATCH_RETURN(); + + // IAppxManifestQualifiedResourceUtf8 + HRESULT STDMETHODCALLTYPE GetLanguage(LPSTR *language) noexcept override try + { + return m_factory->MarshalOutStringUtf8(m_language, language); + } CATCH_RETURN(); + + // IAppxManifestQualifiedResourceInternal + const std::string& GetLanguage() override + { + return m_language; + } + + protected: + ComPtr m_factory; + std::string m_language; + std::uint32_t m_scale; + DX_FEATURE_LEVEL m_DXFeatureLevel; + }; + // Object backed by AppxManifest.xml class AppxManifestObject final : public ComClass, IAppxManifestReader5, IVerifierObject, IAppxManifestObject, IMsixDocumentElement> @@ -319,7 +430,7 @@ namespace MSIX { // IAppxManifestReader2 HRESULT STDMETHODCALLTYPE GetQualifiedResources(IAppxManifestQualifiedResourcesEnumerator **resources) noexcept override; - + // IAppxManifestReader3 HRESULT STDMETHODCALLTYPE GetCapabilitiesByCapabilityClass( APPX_CAPABILITY_CLASS_TYPE capabilityClass, diff --git a/src/inc/internal/AppxPackageWriter.hpp b/src/inc/internal/AppxPackageWriter.hpp index 68af4e4c3..5067c43b3 100644 --- a/src/inc/internal/AppxPackageWriter.hpp +++ b/src/inc/internal/AppxPackageWriter.hpp @@ -43,7 +43,7 @@ namespace MSIX { IAppxPackageWriterUtf8, IAppxPackageWriter3, IAppxPackageWriter3Utf8> { public: - AppxPackageWriter(IMsixFactory* factory, const ComPtr& zip); + AppxPackageWriter(IMsixFactory* factory, const ComPtr& zip, bool enableFileHash); AppxPackageWriter(IPackage* packageToSign, std::unique_ptr&& accumulator); ~AppxPackageWriter() {}; diff --git a/src/inc/internal/BundleManifestWriter.hpp b/src/inc/internal/BundleManifestWriter.hpp new file mode 100644 index 000000000..b4e9bda51 --- /dev/null +++ b/src/inc/internal/BundleManifestWriter.hpp @@ -0,0 +1,79 @@ +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#pragma once + +#include "XmlWriter.hpp" +#include "ComHelper.hpp" +#include "Exceptions.hpp" +#include "UnicodeConversion.hpp" +#include "VersionHelpers.hpp" + +#include + +namespace MSIX { + + // Tracks the internal state of the Bundle Manifest writer + enum ElementWriterState + { + Uninitialized = 0, + BundleManifestStarted = 1, + PackagesAdded = 2, + OptionalBundlesAdded = 3, + BundleManifestEnded = 4, + }; + + struct PackageInfo + { + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE type; + std::uint64_t version; + std::string architecture; + std::string resourceId; + std::string fileName; + std::uint64_t size; + std::uint64_t offset; + ComPtr resources; + bool isDefaultApplicablePackage; + ComPtr tdfs; + }; + + struct OptionalBundleInfo + { + std::string name; + std::string publisher; + std::uint64_t version; + std::string fileName; + std::vector optionalPackages; + }; + + class BundleManifestWriter final + { + public: + BundleManifestWriter(); + void StartBundleManifest(std::string targetXmlNamespace, + std::string name, std::string publisher, std::uint64_t version); + void StartBundleElement(); + void WriteIdentityElement(std::string name, std::string publisher, std::uint64_t version); + void StartPackagesElement(); + void AddPackage(PackageInfo packageInfo); + void WritePackageElement(PackageInfo packageInfo); + void WriteResourcesElement(IAppxManifestQualifiedResourcesEnumerator* resources); + void WriteDependenciesElement(IAppxManifestTargetDeviceFamiliesEnumerator* tdfs); + void AddOptionalBundle(OptionalBundleInfo bundleInfo); + void WriteOptionalBundleElement(OptionalBundleInfo bundleInfo); + void EndPackagesElementIfNecessary(); + void EndPackagesElement(); + void EndBundleManifest(); + void EndBundleElement(); + + ComPtr GetStream() { return m_xmlWriter.GetStream(); } + std::string GetQualifiedName(std::string namespaceAlias, std::string name); + std::string GetElementName(std::string targetNamespace, std::string targetNamespaceAlias, std::string name); + + protected: + XmlWriter m_xmlWriter; + std::string targetXmlNamespace; + std::uint32_t currentState; + }; +} \ No newline at end of file diff --git a/src/inc/internal/BundleValidationHelper.hpp b/src/inc/internal/BundleValidationHelper.hpp new file mode 100644 index 000000000..c5522c89f --- /dev/null +++ b/src/inc/internal/BundleValidationHelper.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "AppxPackaging.hpp" +#include "ComHelper.hpp" +#include "DirectoryObject.hpp" +#include "ZipObjectWriter.hpp" +#include "AppxPackageInfo.hpp" +#include "AppxManifestObject.hpp" +#include "VersionHelpers.hpp" + +#include +#include +#include + +namespace MSIX { + + struct PackageNameInfo + { + std::string packageFullName; + std::string fileName; + }; + + class BundleValidationHelper + { + public: + BundleValidationHelper(); + + void ValidateTargetDeviceFamiliesFromManifestPackageId(IAppxManifestPackageIdInternal* packageId, IAppxManifestTargetDeviceFamiliesEnumerator* tdfs, + std::string fileName); + + bool StartsWith(std::string str, std::string prefix); + + bool ContainsMultipleNeutralAppPackages(); + + bool ContainsApplicationPackage(); + + void ValidateContainsMultipleNeutralAppPackages(bool isPre2018BundleManifest); + + void AddPackage(APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE type, IAppxManifestPackageIdInternal* packageId, std::string fileName); + + void ValidateResourcePackage(IAppxManifestPackageIdInternal* packageId, PackageNameInfo packageNameInfo); + + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE GetPayloadPackageType(IAppxManifestReader* packageManifestReader, std::string fileName); + + void ValidateOSVersion(IAppxManifestReader* packageManifestReader, std::string fileName); + + void ValidateApplicationElement(IAppxManifestReader* packageManifestReader, std::string fileName); + + private: + const std::string MinimumAllowedOSVersion = "6.3.0.0"; + std::uint32_t numberOfNeutralAppPackages = 0; + // This map contains the set of all target device families from neutral application packages that have been added. + std::map neutralTdfs; + bool bundleTargetsRs5OrLess = false; + std::uint32_t MaxNumberOfPackages = 10000; + std::uint32_t numberOfPackagesAdded = 0; + bool containsApplicationPackage = false; + + // This map contains the set of all package file names from resource packages that have been added. + std::map fileNamesMap; + + // This map contains the set of all resource IDs from resource packages that have been added. + std::map resourceIds; + }; +} \ No newline at end of file diff --git a/src/inc/internal/BundleWriterHelper.hpp b/src/inc/internal/BundleWriterHelper.hpp new file mode 100644 index 000000000..103a59d59 --- /dev/null +++ b/src/inc/internal/BundleWriterHelper.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "AppxPackaging.hpp" +#include "ComHelper.hpp" +#include "DirectoryObject.hpp" +#include "AppxBlockMapWriter.hpp" +#include "ContentTypeWriter.hpp" +#include "ZipObjectWriter.hpp" +#include "AppxPackageInfo.hpp" +#include "BundleManifestWriter.hpp" +#include "AppxManifestObject.hpp" +#include "BundleValidationHelper.hpp" + +#include + +namespace MSIX { + + class BundleWriterHelper + { + public: + BundleWriterHelper(); + + std::uint64_t GetStreamSize(IStream* stream); + + void AddPackage(std::string fileName, IAppxPackageReader* packageReader, std::uint64_t bundleOffset, + std::uint64_t packageSize, bool isDefaultApplicableResource); + + void GetValidatedPackageData( + std::string fileName, + IAppxPackageReader* packageReader, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE* packageType, + IAppxManifestPackageId** packageId, + IAppxManifestQualifiedResourcesEnumerator** resources, + IAppxManifestTargetDeviceFamiliesEnumerator** tdfs); + + void AddValidatedPackageData( + std::string fileName, + std::uint64_t bundleOffset, + std::uint64_t packageSize, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType, + ComPtr packageId, + bool isDefaultApplicablePackage, + IAppxManifestQualifiedResourcesEnumerator* resources, + IAppxManifestTargetDeviceFamiliesEnumerator* tdfs); + + void ValidateNameAndPublisher(IAppxManifestPackageIdInternal* packageId, std::string filename); + + void AddPackageInfoToVector(std::vector& packagesVector, PackageInfo packageInfo); + + void EndBundleManifest(); + + ComPtr GetBundleManifestStream() { return m_bundleManifestWriter.GetStream(); } + + void SetBundleVersion(std::uint64_t bundleVersion) { this->bundleVersion = bundleVersion; } + + std::uint64_t GetBundleVersion() { return this->bundleVersion; } + + std::vector GetPayloadPackages() { return payloadPackages; } + + //Add External packages + void AddExternalPackageReferenceFromManifest(std::string fileName, IAppxManifestReader* manifestReader, + bool isDefaultApplicablePackage); + + std::uint64_t GetMinTargetDeviceFamilyVersionFromManifestForWindows(IAppxManifestReader* packageManifestReader); + + private: + std::vector payloadPackages; + std::map optionalBundles; + bool hasExternalPackages; + bool hasDefaultOrNeutralResources; + std::string mainPackageName; + std::string mainPackagePublisher; + std::uint64_t bundleVersion; + + BundleValidationHelper m_validationHelper; + BundleManifestWriter m_bundleManifestWriter; + }; +} \ No newline at end of file diff --git a/src/inc/internal/ContentType.hpp b/src/inc/internal/ContentType.hpp index 12cddfcb6..f431c6d7b 100644 --- a/src/inc/internal/ContentType.hpp +++ b/src/inc/internal/ContentType.hpp @@ -23,6 +23,7 @@ namespace MSIX { static const ContentType& GetContentTypeByExtension(std::string& ext); static const std::string GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE footprintFile); + static const std::string GetBundlePayloadFileContentType(APPX_BUNDLE_FOOTPRINT_FILE_TYPE footprintFile); private: APPX_COMPRESSION_OPTION m_compressionOpt; diff --git a/src/inc/internal/Crypto.hpp b/src/inc/internal/Crypto.hpp index 5ae76d6bd..3522d26b8 100644 --- a/src/inc/internal/Crypto.hpp +++ b/src/inc/internal/Crypto.hpp @@ -3,58 +3,57 @@ // See LICENSE file in the project root for full license information. // #pragma once -#include "Exceptions.hpp" -#include -#include #include -namespace MSIX { +#ifndef SHA256_DIGEST_LENGTH +#define SHA256_DIGEST_LENGTH 32 +#endif - // Forward declaration of type defined within PAL - struct SHA256Context; +namespace MSIX { - // Class used to compute SHA256 hashes over various sets of data. - // Create one and Add data to it if the data is not all available, - // or simply call ComputeHash if the data is all in memory. class SHA256 { public: - using HashBuffer = std::vector; + static bool ComputeHash(const std::uint8_t *buffer, std::uint32_t cbBuffer, std::vector& hash); + /// + /// Construct and initialize the hash engine so it can be used to compute hash of input data. + /// SHA256(); + ~SHA256(); - // Adds the next chunk of data to the hash. - void Add(const uint8_t* buffer, size_t cbBuffer); - - inline void Add(const std::vector& buffer) - { - Add(buffer.data(), buffer.size()); - } + /// + /// Reset the internal state of the hash engine so it can be used again to hash data. + /// + void Reset(); - // Gets the hash of the data. This is a destructive action; the accumulated hash - // value will be returned and the object can no longer be used. - void Get(HashBuffer& hash); + /// + /// Hash data. Can be called repeatedly to hash a stream of data. Call FinalizeAndGetHashValue to finalize the hash engine + /// and get the hash value of all the input data. + /// + /// Buffer containing data + /// Size of the data in bytes + void HashData(const std::uint8_t* buffer, std::uint32_t cbBuffer); - inline HashBuffer Get() - { - HashBuffer result{}; - Get(result); - return result; + /// + /// Hash data. Can be called repeatedly to hash a stream of data. Call FinalizeAndGetHashValue to finalize the hash engine + /// and get the hash value of all the input data. + /// + /// Buffer containing data + inline void HashData(const std::vector& buffer) { + HashData(buffer.data(), static_cast(buffer.size())); } - // Computes the hash of the given buffer immediately. - static bool ComputeHash(uint8_t *buffer, std::uint32_t cbBuffer, HashBuffer& hash); + /// + /// Finalize the hash engine and get the computed hash value of all the input data from HashData calls. + /// After this call, the hash engine cannot be used again until Reset is called to reset its state. + /// + /// Output bufer to receive the computed hash value. + void FinalizeAndGetHashValue(std::vector& hash); private: - void EnsureNotFinished() const { ThrowErrorIfNot(Error::InvalidState, context, "The hash is already finished"); } - - struct SHA256ContextDeleter - { - void operator()(SHA256Context* context); - }; - - std::unique_ptr context; + void* m_hashContext = nullptr; }; class Base64 @@ -62,4 +61,4 @@ namespace MSIX { public: static std::string ComputeBase64(const std::vector& buffer); }; -} \ No newline at end of file +} diff --git a/src/inc/internal/DirectoryObject.hpp b/src/inc/internal/DirectoryObject.hpp index 0fbe67432..d3c34b601 100644 --- a/src/inc/internal/DirectoryObject.hpp +++ b/src/inc/internal/DirectoryObject.hpp @@ -58,7 +58,7 @@ namespace MSIX { ComPtr OpenFile(const std::string& fileName, MSIX::FileStream::Mode mode) override; std::multimap GetFilesByLastModDate() override; - static const char* GetPathSeparator(); + char GetPathSeparator() const; protected: std::string m_root; diff --git a/src/inc/internal/FileNameValidation.hpp b/src/inc/internal/FileNameValidation.hpp index 08914f947..72d6818f8 100644 --- a/src/inc/internal/FileNameValidation.hpp +++ b/src/inc/internal/FileNameValidation.hpp @@ -13,7 +13,7 @@ namespace MSIX { { static bool IsFileNameValid(const std::string& name); static bool IsIdentifierValid(const std::string& name); - static bool IsFootPrintFile(const std::string& fileName); + static bool IsFootPrintFile(const std::string& fileName, bool isBundle); static bool IsReservedFolder(const std::string& fileName); }; } diff --git a/src/inc/internal/IXml.hpp b/src/inc/internal/IXml.hpp index 49504e5c7..c9eef2491 100644 --- a/src/inc/internal/IXml.hpp +++ b/src/inc/internal/IXml.hpp @@ -77,6 +77,7 @@ enum class XmlAttributeName : std::uint8_t Package_Applications_Application_Id, Category, MaxMajorVersionTested, + DXFeatureLevel, }; // {ac94449e-442d-4bed-8fca-83770c0f7ee9} diff --git a/src/inc/internal/MappingFileParser.hpp b/src/inc/internal/MappingFileParser.hpp new file mode 100644 index 000000000..954dd6b42 --- /dev/null +++ b/src/inc/internal/MappingFileParser.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "MSIXWindows.hpp" +#include "MsixErrors.hpp" +#include "Exceptions.hpp" + +#include +#include + +namespace MSIX { + + typedef enum : UINT32 + { + Continue = 0, + Stop, + SkipSection, + Fail + } HandlerState; + + enum SectionID + { + UnknownSection = -1, + FilesSection = 0, + ResourceMetadataSection = 1, + KeysSection = 2, + ExternalPackagesSection = 3, + }; + + const std::map KnownSectionNamesMap = + {{"Files", FilesSection}, + {"ResourceMetadata", ResourceMetadataSection}, + {"Keys", KeysSection}, + {"ExternalPackages", ExternalPackagesSection}}; + + class MappingFileParser final + { + public: + MappingFileParser(); + void ParseMappingFile(std::string mappingFile); + bool IsSectionFound(SectionID sectionId); + std::map GetFileList() const { return this->list; } + std::map GetExternalPackagesList() const { return this->externalPackagesList; } + + protected: + HandlerState ParseSectionHeading(std::string line); + HandlerState HandleSection(std::string sectionName); + SectionID GetSectionIDByName(std::string sectionName); + + HandlerState ParseMapping(std::string line, const char* firstChar); + HandlerState HandleMapping(std::vector pathTokens); + std::pair SearchString(const char* line, char c); + bool IsWhitespace(char c); + + /// Returns a pointer to the first non-whitespace character in a string + const char* SkipWhitespace(const char* line); + std::string RemoveTrailingWhitespace(std::string line); + + int lineNumber; + static const int NumKnownSections = 4; + std::map list; + std::map externalPackagesList; + bool foundSection[NumKnownSections] = {0}; + SectionID currentSectionId; + std::string errorMessage; + }; +} + diff --git a/src/inc/internal/SHA256HashStream.hpp b/src/inc/internal/SHA256HashStream.hpp index f11f121bc..fa4697887 100644 --- a/src/inc/internal/SHA256HashStream.hpp +++ b/src/inc/internal/SHA256HashStream.hpp @@ -17,7 +17,7 @@ namespace MSIX { HRESULT STDMETHODCALLTYPE Write(const void* buffer, ULONG countBytes, ULONG* bytesWritten) noexcept override try { - m_hasher.Add(reinterpret_cast(buffer), countBytes); + m_hasher.HashData(reinterpret_cast(buffer), countBytes); if (bytesWritten) { *bytesWritten = countBytes; @@ -25,9 +25,9 @@ namespace MSIX { return static_cast(Error::OK); } CATCH_RETURN(); - SHA256::HashBuffer GetHash() + void FinalizeAndGetHashValue(std::vector& hash) { - return m_hasher.Get(); + m_hasher.FinalizeAndGetHashValue(hash); } private: diff --git a/src/inc/internal/VersionHelpers.hpp b/src/inc/internal/VersionHelpers.hpp new file mode 100644 index 000000000..8fa519e0b --- /dev/null +++ b/src/inc/internal/VersionHelpers.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "MSIXWindows.hpp" +#include "UnicodeConversion.hpp" + +namespace MSIX { + + std::uint64_t ConvertVersionStringToUint64(const std::string& versionString); + + std::string ConvertVersionToString(std::uint64_t version); +} + diff --git a/src/inc/internal/XmlWriter.hpp b/src/inc/internal/XmlWriter.hpp index 7bfa15503..e4e059997 100644 --- a/src/inc/internal/XmlWriter.hpp +++ b/src/inc/internal/XmlWriter.hpp @@ -14,6 +14,7 @@ namespace MSIX { // common attribute names static const char* xmlnsAttribute = "xmlns"; + static const char* xmlNamespaceDelimiter = ":"; // This is a super light xml writer that doesn't use any xml libraries and // just writes to a stream the basics of an xml file. diff --git a/src/inc/internal/ZipObjectWriter.hpp b/src/inc/internal/ZipObjectWriter.hpp index d1d9dbbb5..56863ddcf 100644 --- a/src/inc/internal/ZipObjectWriter.hpp +++ b/src/inc/internal/ZipObjectWriter.hpp @@ -15,6 +15,8 @@ #include #include +#include + // {350dd671-0c40-4cd7-9a5b-27456d604bd0} #ifndef WIN32 interface IZipWriter : public IUnknown diff --git a/src/inc/public/AppxPackaging.hpp b/src/inc/public/AppxPackaging.hpp index 9fb4bbd40..0f348ac39 100644 --- a/src/inc/public/AppxPackaging.hpp +++ b/src/inc/public/AppxPackaging.hpp @@ -78,6 +78,7 @@ SpecializeUuidOfImpl(IAppxManifestQualifiedResourcesEnumerator); SpecializeUuidOfImpl(IAppxManifestQualifiedResource); SpecializeUuidOfImpl(IAppxBundleFactory); SpecializeUuidOfImpl(IAppxBundleWriter); +SpecializeUuidOfImpl(IAppxBundleWriter4); SpecializeUuidOfImpl(IAppxBundleReader); SpecializeUuidOfImpl(IAppxBundleManifestReader); SpecializeUuidOfImpl(IAppxBundleManifestPackageInfoEnumerator); @@ -124,6 +125,7 @@ interface IAppxManifestQualifiedResourcesEnumerator; interface IAppxManifestQualifiedResource; interface IAppxBundleFactory; interface IAppxBundleWriter; +interface IAppxBundleWriter4; interface IAppxBundleReader; interface IAppxBundleManifestReader; interface IAppxBundleManifestPackageInfoEnumerator; @@ -970,6 +972,31 @@ enum tagLOCKTYPE }; #endif /* __IAppxBundleWriter_INTERFACE_DEFINED__ */ +#ifndef __IAppxBundleWriter4_INTERFACE_DEFINED__ +#define __IAppxBundleWriter4_INTERFACE_DEFINED__ + + // {9CD9D523-5009-4C01-9882-DC029FBD47A3} + MSIX_INTERFACE(IAppxBundleWriter4,0x9cd9d523,0x5009,0x4c01,0x98,0x82,0xdc,0x02,0x9f,0xbd,0x47,0xa3); + interface IAppxBundleWriter4 : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE AddPayloadPackage( + /* [string][in] */ LPCWSTR fileName, + /* [in] */ IStream* packageStream, + /* [in] */ BOOL isDefaultApplicablePackage) noexcept = 0; + + virtual HRESULT AddPackageReference( + /* [string][in] */ LPCWSTR fileName, + /* [in] */ IStream* inputStream, + /* [in] */ BOOL isDefaultApplicablePackage) noexcept = 0; + + virtual HRESULT AddExternalPackageReference( + /* [string][in] */ LPCWSTR fileName, + /* [in] */ IStream* inputStream, + /* [in] */ BOOL isDefaultApplicablePackage) noexcept = 0; + }; +#endif /* __IAppxBundleWriter4_INTERFACE_DEFINED__ */ + #ifndef __IAppxBundleReader_INTERFACE_DEFINED__ #define __IAppxBundleReader_INTERFACE_DEFINED__ @@ -1172,6 +1199,7 @@ interface IMsixElementEnumerator; interface IMsixFactoryOverrides; interface IMsixStreamFactory; interface IMsixApplicabilityLanguagesEnumerator; +interface IMsixPackageWriterFactory; #ifndef __IMsixDocumentElement_INTERFACE_DEFINED__ #define __IMsixDocumentElement_INTERFACE_DEFINED__ @@ -1632,6 +1660,7 @@ enum MSIX_VALIDATION_OPTION // If the SDK is compiled without USE_VALIDATION_PARSER, // no schema validation is done, but it needs to be // valid xml. + MSIX_VALIDATION_OPTION_SKIPPACKAGEVALIDATION = 0x8, } MSIX_VALIDATION_OPTION; typedef /* [v1_enum] */ @@ -1665,11 +1694,29 @@ enum MSIX_APPLICABILITY_OPTIONS MSIX_APPLICABILITY_OPTION_SKIPLANGUAGE = 0x2, } MSIX_APPLICABILITY_OPTIONS; +typedef /* [v1_enum] */ +enum MSIX_BUNDLE_OPTIONS + { + MSIX_OPTION_NONE = 0x0, + MSIX_OPTION_VERBOSE = 0x1, + MSIX_OPTION_OVERWRITE = 0x2, + MSIX_OPTION_NOOVERWRITE = 0x4, + MSIX_OPTION_VERSION = 0x8, + MSIX_BUNDLE_OPTION_FLATBUNDLE = 0x10, + MSIX_BUNDLE_OPTION_BUNDLEMANIFESTONLY = 0x20, + } MSIX_BUNDLE_OPTIONS; + +typedef /* [v1_enum] */ +enum MSIX_FACTORY_OPTIONS +{ + MSIX_FACTORY_OPTION_NONE = 0x0, + MSIX_FACTORY_OPTION_WRITER_ENABLE_FILE_HASH = 0x1, // The package writer will compute full file hash and add element in block map xml +} MSIX_FACTORY_OPTIONS; + #define MSIX_VALIDATION_NONE static_cast( \ MSIX_VALIDATION_OPTION_SKIPSIGNATURE \ ) - #define MSIX_PLATFORM_ALL MSIX_PLATFORM_WINDOWS10 | \ MSIX_PLATFORM_WINDOWS10 | \ MSIX_PLATFORM_WINDOWS8 | \ @@ -1757,6 +1804,14 @@ MSIX_API HRESULT STDMETHODCALLTYPE SignPackage( LPCSTR privateKey ) noexcept; +MSIX_API HRESULT STDMETHODCALLTYPE PackBundle( + MSIX_BUNDLE_OPTIONS bundleOptions, + char* directoryPath, + char* outputBundle, + char* mappingFile, + char* version +) noexcept; + #endif // MSIX_PACK // A call to called CoCreateAppxFactory is required before start using the factory on non-windows platforms specifying @@ -1764,19 +1819,41 @@ MSIX_API HRESULT STDMETHODCALLTYPE SignPackage( typedef LPVOID STDMETHODCALLTYPE COTASKMEMALLOC(SIZE_T cb); typedef void STDMETHODCALLTYPE COTASKMEMFREE(LPVOID pv); -MSIX_API HRESULT STDMETHODCALLTYPE GetLogTextUTF8(COTASKMEMALLOC* memalloc, char** logText) noexcept; +MSIX_API HRESULT STDMETHODCALLTYPE MsixGetLogTextUTF8(COTASKMEMALLOC* memalloc, char** logText) noexcept; + +#ifndef MSIX_DEFINE_GetLogTextUTF8_BACKCOMPAT +#define MSIX_DEFINE_GetLogTextUTF8_BACKCOMPAT 1 +#endif + +#if MSIX_DEFINE_GetLogTextUTF8_BACKCOMPAT +#ifndef GetLogTextUTF8 +#define GetLogTextUTF8(memalloc, logText) MsixGetLogTextUTF8(memalloc, logText) +#endif +#endif // Call specific for Windows. Default to call CoTaskMemAlloc and CoTaskMemFree MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactory( MSIX_VALIDATION_OPTION validationOption, IAppxFactory** appxFactory) noexcept; +MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactoryWithOptions( + MSIX_VALIDATION_OPTION validationOption, + MSIX_FACTORY_OPTIONS factoryOptions, + IAppxFactory** appxFactory) noexcept; + MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactoryWithHeap( COTASKMEMALLOC* memalloc, COTASKMEMFREE* memfree, MSIX_VALIDATION_OPTION validationOption, IAppxFactory** appxFactory) noexcept; +MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactoryWithHeapAndOptions( + COTASKMEMALLOC* memalloc, + COTASKMEMFREE* memfree, + MSIX_VALIDATION_OPTION validationOption, + MSIX_FACTORY_OPTIONS factoryOptions, + IAppxFactory** appxFactory) noexcept; + MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxBundleFactory( MSIX_VALIDATION_OPTION validationOption, MSIX_APPLICABILITY_OPTIONS applicabilityOptions, @@ -1802,4 +1879,4 @@ MSIX_API HRESULT STDMETHODCALLTYPE CreateStreamOnFileUTF16( } // extern "C++" -#endif //__appxpackaging_hpp__ \ No newline at end of file +#endif //__appxpackaging_hpp__ diff --git a/src/inc/public/MsixErrors.hpp b/src/inc/public/MsixErrors.hpp index 578dc90e2..7775f60aa 100644 --- a/src/inc/public/MsixErrors.hpp +++ b/src/inc/public/MsixErrors.hpp @@ -27,6 +27,10 @@ namespace MSIX { InvalidParameter = 0x80070057, Stg_E_Invalidpointer = 0x80030009, InvalidState = 0x804d0003, + BadFormat = 0x8007000b, + InvalidData = 0x8007000d, + OutOfBounds = 0x8000000b, + PackagingErrorInternal = 0x80080200, // // msix specific error codes diff --git a/src/makemsix/main.cpp b/src/makemsix/main.cpp index 7f035b4a5..09bab93c0 100644 --- a/src/makemsix/main.cpp +++ b/src/makemsix/main.cpp @@ -22,7 +22,7 @@ struct Invocation; struct Option { // Constructor for flags; they can't be required and don't take parameters. - Option(std::string name, std::string help) : + Option(std::string name, std::string help) : Name(std::move(name)), Required(false), ParameterCount(0), Help(std::move(help)) {} @@ -432,6 +432,43 @@ MSIX_APPLICABILITY_OPTIONS GetApplicabilityOption(const Invocation& invocation) return applicability; } +MSIX_BUNDLE_OPTIONS GetBundleOptions(const Invocation& invocation) +{ + MSIX_BUNDLE_OPTIONS bundleOptions = MSIX_BUNDLE_OPTIONS::MSIX_OPTION_NONE; + + if (invocation.IsOptionPresent("-v")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_OPTION_VERBOSE; + } + + if (invocation.IsOptionPresent("-o")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_OPTION_OVERWRITE; + } + + if (invocation.IsOptionPresent("-no")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_OPTION_NOOVERWRITE; + } + + if (invocation.IsOptionPresent("-bv")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_OPTION_VERSION; + } + + if (invocation.IsOptionPresent("-fb")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_BUNDLE_OPTION_FLATBUNDLE; + } + + if (invocation.IsOptionPresent("-mo")) + { + bundleOptions |= MSIX_BUNDLE_OPTIONS::MSIX_BUNDLE_OPTION_BUNDLEMANIFESTONLY; + } + + return bundleOptions; +} + #ifdef MSIX_PACK MSIX_CERTIFICATE_FORMAT GetCertificateFormat(const Invocation& invocation) { @@ -586,6 +623,67 @@ Command CreatePackCommand() return result; } +Command CreateBundleCommand() +{ + Command result{ "bundle", "Create a new app bundle from files on disk", + { + Option{ "-d", "Input directory path.", false, 1, "inputDirectory" }, + Option{ "-p", "Output bundle file path.", true, 1, "outputBundle" }, + Option{ "-f", "Mapping file path.", false, 1, "mappingFile" }, + Option{ "-bv", "Specifies the version number of the bundle being created. The version" + "must be in dotted - quad notation of four integers" + "... ranging from 0 to 65535 each.If the" + "/ bv option is not specified or is set to 0.0.0.0, the bundle is created" + "using the current date - time formatted as the version :" + "....", false, 1, "version" }, + Option{ "-mo", "Generates a bundle manifest only, instead of a full bundle. Input files must all " + "be package manifests in XML format if this option is specified." }, + Option{ "-fb", "Generates a fully sparse bundle where all packages are references to" + "packages that exist outside of the bundle file." }, + Option{ "-o", "Forces the output to overwrite any existing files with the" + "same name.By default, the user is asked whether to overwrite existing" + "files with the same name.You can't use this option with /no." }, + Option{ "-no","Prevents the output from overwriting any existing files" + "with the same name.By default, the user is asked whether to overwrite" + "existing files with the same name.You can't use this option with /o." }, + Option{ "-v", "Enables verbose output of messages to the console."}, + Option{ TOOL_HELP_COMMAND_STRING, "Displays this help text." }, + } + }; + + result.SetDescription({ + "Creates an app bundle at by adding all files from", + "either (including subfolders) or a list of files within" + ".If either source contains a bundle manifest, it will be" + "ignored." + + "Using / p will result in the bundle being unencrypted, while using / ep will" + "result in the bundle being encrypted.If you use / ep you must specify" + "either / kt or /kf.", + }); + + result.SetInvocationFunc([](const Invocation& invocation) + { + char* directoryPath = (invocation.IsOptionPresent("-d")) ? + const_cast(invocation.GetOptionValue("-d").c_str()) : nullptr; + + char* mappingFile = (invocation.IsOptionPresent("-f")) ? + const_cast(invocation.GetOptionValue("-f").c_str()) : nullptr; + + char* version = (invocation.IsOptionPresent("-bv")) ? + const_cast(invocation.GetOptionValue("-bv").c_str()) : nullptr; + + return PackBundle( + GetBundleOptions(invocation), + directoryPath, + const_cast(invocation.GetOptionValue("-p").c_str()), + mappingFile, + version); + }); + + return result; +} + Command CreateSignCommand() { Command result{ "sign", "Signs an existing package in-place", @@ -645,6 +743,7 @@ int main(int argc, char* argv[]) CreateUnbundleCommand(), #ifdef MSIX_PACK CreatePackCommand(), + CreateBundleCommand(), CreateSignCommand(), #endif }; @@ -683,7 +782,7 @@ int main(int argc, char* argv[]) } Text text; - auto logResult = GetLogTextUTF8(MyAllocate, &text); + auto logResult = MsixGetLogTextUTF8(MyAllocate, &text); if (0 == logResult) { std::cout << "LOG:" << std::endl << text.content << std::endl; @@ -694,4 +793,4 @@ int main(int argc, char* argv[]) } } return result; -} \ No newline at end of file +} diff --git a/src/msix/CMakeLists.txt b/src/msix/CMakeLists.txt index 8ad39be75..12f56cb49 100644 --- a/src/msix/CMakeLists.txt +++ b/src/msix/CMakeLists.txt @@ -19,6 +19,7 @@ list(APPEND MSIX_UNPACK_EXPORTS if(MSIX_PACK) list(APPEND MSIX_PACK_EXPORTS "PackPackage" + "PackBundle" "SignPackage" ) endif() @@ -26,9 +27,11 @@ endif() list(APPEND MSIX_EXPORTS "CoCreateAppxFactory" "CoCreateAppxFactoryWithHeap" + "CoCreateAppxFactoryWithOptions" + "CoCreateAppxFactoryWithHeapAndOptions" "CreateStreamOnFile" "CreateStreamOnFileUTF16" - "GetLogTextUTF8" + "MsixGetLogTextUTF8" "CoCreateAppxBundleFactory" "CoCreateAppxBundleFactoryWithHeap" ${MSIX_UNPACK_EXPORTS} @@ -131,6 +134,12 @@ if(MSIX_PACK) pack/ContentType.cpp pack/DeflateStream.cpp pack/ZipObjectWriter.cpp + pack/BundleManifestWriter.cpp + pack/BundleWriterHelper.cpp + pack/AppxBundleWriter.cpp + pack/VersionHelpers.cpp + pack/MappingFileParser.cpp + pack/BundleValidationHelper.cpp pack/Signing.cpp ) endif() @@ -238,7 +247,7 @@ endforeach() add_library(${PROJECT_NAME} SHARED msix.cpp ${MsixSrc} -) + ) # Adding dependency to the third party libs directory add_dependencies(${PROJECT_NAME} LIBS) @@ -274,7 +283,7 @@ if(WIN32) "/DELAYLOAD:api-ms-win-core-winrt-l1-1-0.dll") string(REPLACE ";" " " DELAYFLAGS "${DELAYFLAGS}") set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY LINK_FLAGS "${DELAYFLAGS} /LTCG") - set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY LINK_FLAGS " /DEF:windowsexports.def") + set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY LINK_FLAGS " /DEF:${CMAKE_CURRENT_BINARY_DIR}/windowsexports.def") if(USE_STATIC_MSVC) if(CMAKE_BUILD_TYPE MATCHES Debug) set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY LINK_FLAGS " /NODEFAULTLIB:MSVCRTD") diff --git a/src/msix/PAL/Crypto/OpenSSL/Crypto.cpp b/src/msix/PAL/Crypto/OpenSSL/Crypto.cpp index e05eba818..468933da4 100644 --- a/src/msix/PAL/Crypto/OpenSSL/Crypto.cpp +++ b/src/msix/PAL/Crypto/OpenSSL/Crypto.cpp @@ -1,60 +1,58 @@ -// -// Copyright (C) 2017 Microsoft. All rights reserved. -// See LICENSE file in the project root for full license information. -// -#include "Exceptions.hpp" -#include "Crypto.hpp" -#include "SharedOpenSSL.hpp" - -#include "openssl/sha.h" -#include "openssl/evp.h" - -namespace MSIX { - - struct SHA256Context : SHA256_CTX - { - }; - - SHA256::SHA256() : context(new SHA256Context{}) - { - ThrowOpenSSLErrIfFailed(SHA256_Init(context.get())); - } - - void SHA256::Add(const uint8_t* buffer, size_t cbBuffer) - { - EnsureNotFinished(); - - ThrowOpenSSLErrIfFailed(SHA256_Update(context.get(), buffer, cbBuffer)); - } - - void SHA256::Get(HashBuffer& hash) - { - EnsureNotFinished(); - - hash.resize(SHA256_DIGEST_LENGTH); - ThrowOpenSSLErrIfFailed(SHA256_Final(hash.data(), context.get())); - - context.reset(); - } - - bool SHA256::ComputeHash(std::uint8_t *buffer, std::uint32_t cbBuffer, HashBuffer& hash) - { - hash.resize(SHA256_DIGEST_LENGTH); - ::SHA256(buffer, cbBuffer, hash.data()); - return true; - } - - void SHA256::SHA256ContextDeleter::operator()(SHA256Context* context) - { - delete context; - } - - std::string Base64::ComputeBase64(const std::vector& buffer) - { - int expectedSize = static_cast(((buffer.size() + 2) / 3) * 4); // +2 for a cheap round up if it needs padding - std::vector result(expectedSize + 1); // +1 for the null character - int encodeResult = EVP_EncodeBlock(static_cast(result.data()), const_cast(buffer.data()), static_cast(buffer.size())); - ThrowErrorIf(Error::Unexpected, expectedSize != encodeResult, "Error computing base64"); - return std::string(result.begin(), result.end() - 1); // remove the null character - } -} +// +// Copyright (C) 2017 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#include "Exceptions.hpp" +#include "Crypto.hpp" + +#include "openssl/sha.h" +#include "openssl/evp.h" + +namespace MSIX { + SHA256::SHA256() + { + m_hashContext = new SHA256_CTX; + Reset(); + } + + SHA256::~SHA256() + { + if (m_hashContext != nullptr) + { + // Linux, aosp (Android) and iOS compilers do not allow delete a void pointer, hence the casting. + delete (SHA256_CTX*)m_hashContext; + } + } + + void SHA256::Reset() + { + ThrowErrorIfNot(Error::Unexpected, ::SHA256_Init((SHA256_CTX*)m_hashContext), "SHA256_Init failed"); + } + + void SHA256::HashData(const std::uint8_t* buffer, std::uint32_t cbBuffer) + { + ThrowErrorIfNot(Error::Unexpected, ::SHA256_Update((SHA256_CTX*)m_hashContext, buffer, cbBuffer), "SHA256_Update failed"); + } + + void SHA256::FinalizeAndGetHashValue(std::vector& hash) + { + hash.resize(SHA256_DIGEST_LENGTH); + ThrowErrorIfNot(Error::Unexpected, ::SHA256_Final(hash.data(), (SHA256_CTX*)m_hashContext), "SHA256_Final failed"); + } + + bool SHA256::ComputeHash(const std::uint8_t *buffer, std::uint32_t cbBuffer, std::vector& hash) + { + hash.resize(SHA256_DIGEST_LENGTH); + ::SHA256(buffer, cbBuffer, hash.data()); + return true; + } + + std::string Base64::ComputeBase64(const std::vector& buffer) + { + int expectedSize = ((buffer.size() +2)/3)*4; // +2 for a cheap round up if it needs padding + std::vector result(expectedSize); + int encodeResult = EVP_EncodeBlock(static_cast(result.data()), const_cast(buffer.data()), buffer.size()); + ThrowErrorIf(Error::Unexpected, expectedSize != encodeResult, "Error computing base64"); + return std::string(result.begin(), result.end()); + } +} diff --git a/src/msix/PAL/Crypto/OpenSSL/SignatureCreator.cpp b/src/msix/PAL/Crypto/OpenSSL/SignatureCreator.cpp index 27542f218..f56f157cc 100644 --- a/src/msix/PAL/Crypto/OpenSSL/SignatureCreator.cpp +++ b/src/msix/PAL/Crypto/OpenSSL/SignatureCreator.cpp @@ -222,6 +222,7 @@ namespace MSIX IStream* signingCertificate, IStream* privateKey) { + // TODO: likely needs init thread safety as in SignatureValidator::Validate OpenSSL_add_all_algorithms(); CustomOpenSSLObjects customObjects{}; diff --git a/src/msix/PAL/Crypto/OpenSSL/SignatureValidator.cpp b/src/msix/PAL/Crypto/OpenSSL/SignatureValidator.cpp index 6036026d9..65342bef2 100644 --- a/src/msix/PAL/Crypto/OpenSSL/SignatureValidator.cpp +++ b/src/msix/PAL/Crypto/OpenSSL/SignatureValidator.cpp @@ -12,11 +12,33 @@ #include #include #include +#include +#include #include "SharedOpenSSL.hpp" namespace MSIX { + // OpenSSL thread-safety callbacks + static void CryptoLockingCallback(std::int32_t mode, std::int32_t n, char const*, std::int32_t) + { + static std::mutex locks[CRYPTO_NUM_LOCKS]; + + if (mode & CRYPTO_LOCK) + { + locks[n].lock(); + } + else + { + locks[n].unlock(); + } + } + + static void CryptoThreadIDCallback(CRYPTO_THREADID* id) + { + CRYPTO_THREADID_set_numeric(id, std::hash{}(std::this_thread::get_id())); + } + // Best effort to determine whether the signature file is associated with a store cert static bool IsStoreOrigin(std::uint8_t* signatureBuffer, std::uint32_t cbSignatureBuffer) { @@ -40,13 +62,12 @@ namespace MSIX { M_ASN1_OCTET_STRING_print(extbio.get(), ext->value); } - // null terminate the string. - BIO_write(extbio.get(), "", 1); + BUF_MEM *bptr = nullptr; BIO_get_mem_ptr(extbio.get(), &bptr); if (bptr && bptr->data && - std::string((char*)bptr->data).find(OID::WindowsStore()) != std::string::npos) + std::string((char*)bptr->data, bptr->length).find(OID::WindowsStore()) != std::string::npos) { return true; } @@ -265,7 +286,17 @@ namespace MSIX unique_PKCS7 p7(d2i_PKCS7_bio(bmem.get(), nullptr)); // Tell OpenSSL to use all available algorithms when evaluating certs - OpenSSL_add_all_algorithms(); + static std::once_flag sslInitializationFlag; + std::call_once(sslInitializationFlag, [] + { + // Best effort to check if OpenSSL isn't initialized by the app or another library + if (CRYPTO_THREADID_get_callback() == nullptr) + { + OpenSSL_add_all_algorithms(); + CRYPTO_THREADID_set_callback(CryptoThreadIDCallback); + CRYPTO_set_locking_callback(CryptoLockingCallback); + } + }); // Create a trusted cert store unique_X509_STORE store(X509_STORE_new()); diff --git a/src/msix/PAL/Crypto/Win32/Crypto.cpp b/src/msix/PAL/Crypto/Win32/Crypto.cpp index 8ebc28a4b..9f1b7ca55 100644 --- a/src/msix/PAL/Crypto/Win32/Crypto.cpp +++ b/src/msix/PAL/Crypto/Win32/Crypto.cpp @@ -44,94 +44,94 @@ namespace MSIX { { MSIX::RaiseException(__LINE__, __FILE__, m, _status); \ } \ } + + SHA256::SHA256() + { + Reset(); + } - struct SHA256Context + SHA256::~SHA256() { - unique_alg_handle algHandle; - unique_hash_handle hashHandle; - DWORD hashLength = 0; - }; + if (m_hashContext != nullptr) + { + (void)BCryptDestroyHash(m_hashContext); + } + } - SHA256::SHA256() : context(new SHA256Context{}) + void SHA256::Reset() { + if (m_hashContext != nullptr) + { + (void)BCryptDestroyHash(m_hashContext); + m_hashContext = nullptr; + } + BCRYPT_HASH_HANDLE hashHandleT; - DWORD hashLength = 0; - DWORD resultLength = 0; + BCRYPT_ALG_HANDLE algHandleT; // Open an algorithm handle - BCRYPT_ALG_HANDLE algHandleT{}; + // This code passes BCRYPT_HASH_REUSABLE_FLAG with BCryptAlgorithmProvider(...) to load a provider which supports reusable hash ThrowStatusIfFailed(BCryptOpenAlgorithmProvider( - &algHandleT, // Alg Handle pointer + &algHandleT, // Alg Handle pointer BCRYPT_SHA256_ALGORITHM, // Cryptographic Algorithm name (null terminated unicode string) nullptr, // Provider name; if null, the default provider is loaded - 0), // Flags - "failed opening SHA256 algorithm provider"); - context->algHandle.reset(algHandleT); - - // Obtain the length of the hash - ThrowStatusIfFailed(BCryptGetProperty( - context->algHandle.get(), // Handle to a CNG object - BCRYPT_HASH_LENGTH, // Property name (null terminated unicode string) - (PBYTE)&(context->hashLength), // Address of the output buffer which receives the property value - sizeof(context->hashLength), // Size of the buffer in bytes - &resultLength, // Number of bytes that were copied into the buffer - 0), // Flags - "failed getting SHA256 hash length"); - ThrowErrorIf(Error::Unexpected, (resultLength != sizeof(context->hashLength)), "failed getting SHA256 hash length"); + 0), // Flags; Loads a provider which supports reusable hash + "failed computing SHA256 hash"); + unique_alg_handle algHandle(algHandleT); // Create a hash handle ThrowStatusIfFailed(BCryptCreateHash( - context->algHandle.get(), // Handle to an algorithm provider + algHandle.get(), // Handle to an algorithm provider &hashHandleT, // A pointer to a hash handle - can be a hash or hmac object nullptr, // Pointer to the buffer that receives the hash/hmac object 0, // Size of the buffer in bytes nullptr, // A pointer to a key to use for the hash or MAC 0, // Size of the key in bytes 0), // Flags - "failed creating SHA256 hash object"); - context->hashHandle.reset(hashHandleT); - } + "failed computing SHA256 hash"); - void SHA256::Add(const uint8_t* buffer, size_t cbBuffer) + m_hashContext = hashHandleT; + } + + void SHA256::HashData(const std::uint8_t* buffer, std::uint32_t cbBuffer) { - EnsureNotFinished(); + ThrowErrorIf(Error::InvalidState, m_hashContext == nullptr, "HashData is called before hash context is initialized."); - // Add the data - ThrowStatusIfFailed(BCryptHashData(context->hashHandle.get(), const_cast(buffer), static_cast(cbBuffer), 0), "failed adding SHA256 data"); + ThrowStatusIfFailed(BCryptHashData( + m_hashContext, // Handle to the hash or MAC object + (PBYTE)buffer, // A pointer to a buffer that contains the data to hash + cbBuffer, // Size of the buffer in bytes + 0), // Flags + "failed computing SHA256 hash"); } - void SHA256::Get(HashBuffer& hash) + void SHA256::FinalizeAndGetHashValue(std::vector& hash) { - EnsureNotFinished(); + ThrowErrorIf(Error::InvalidState, m_hashContext == nullptr, "HashData is called before hash context is initialized."); // Size the hash buffer appropriately - hash.resize(context->hashLength); + hash.resize(SHA256_DIGEST_LENGTH); // Obtain the hash of the message(s) into the hash buffer ThrowStatusIfFailed(BCryptFinishHash( - context->hashHandle.get(), // Handle to the hash or MAC object + m_hashContext, // Handle to the hash or MAC object hash.data(), // A pointer to a buffer that receives the hash or MAC value - context->hashLength, // Size of the buffer in bytes + static_cast(hash.size()), // Size of the buffer in bytes 0), // Flags - "failed getting SHA256 hash"); + "failed computing SHA256 hash"); - context.reset(); + (void)BCryptDestroyHash(m_hashContext); + m_hashContext = nullptr; } - bool SHA256::ComputeHash(std::uint8_t* buffer, std::uint32_t cbBuffer, HashBuffer& hash) + bool SHA256::ComputeHash(const std::uint8_t* buffer, std::uint32_t cbBuffer, std::vector& hash) { - SHA256 hasher; - hasher.Add(buffer, cbBuffer); - hasher.Get(hash); - + SHA256 hashEngine; + hashEngine.HashData(buffer, cbBuffer); + hashEngine.FinalizeAndGetHashValue(hash); return true; } - void SHA256::SHA256ContextDeleter::operator()(SHA256Context* context) - { - delete context; - } - std::string Base64::ComputeBase64(const std::vector& buffer) { std::wstring result; diff --git a/src/msix/PAL/Crypto/Win32/SignatureValidator.cpp b/src/msix/PAL/Crypto/Win32/SignatureValidator.cpp index f140a3766..d433b93a6 100644 --- a/src/msix/PAL/Crypto/Win32/SignatureValidator.cpp +++ b/src/msix/PAL/Crypto/Win32/SignatureValidator.cpp @@ -1,7 +1,7 @@ // // Copyright (C) 2017 Microsoft. All rights reserved. // See LICENSE file in the project root for full license information. -// +// #include #include #include @@ -18,7 +18,7 @@ namespace MSIX struct unique_local_alloc_deleter { void operator()(HLOCAL h) const { LocalFree(h); }; }; - + struct unique_cert_context_deleter { void operator()(PCCERT_CONTEXT p) const { CertFreeCertificateContext(p); }; }; @@ -115,7 +115,7 @@ namespace MSIX ThrowErrorIfNot(Error::SignatureInvalid, ( CryptMsgGetParam(signedMessage.get(), CMSG_SIGNER_INFO_PARAM, 0, signerInfo, &signerInfoSize) ), "CryptMsgGetParam failed"); - + // Get the signing certificate from the certificate store based on the issuer and serial number of the signer info CERT_INFO certInfo; certInfo.Issuer = signerInfo->Issuer; @@ -126,9 +126,10 @@ namespace MSIX &certInfo)); ThrowErrorIf(Error::SignatureInvalid, (signingCertContext.get() == NULL), "failed to get signing cert context."); - // Get the signing certificate chain context. Do not connect online for URL - // retrievals. Note that this check does not respect the lifetime signing - // EKU on the signing certificate. + // Get the signing certificate chain context. Do not connect online for URL + // retrievals. If CertVerifyCertificateChainPolicy fails to validate the certificates + // we call WinVerifyTrust, which also checks if a package was timestamped while the cert was valid. + // If it returns ERROR_SUCCESS or "0" the signature is valid. CERT_CHAIN_PARA certChainParameters = { 0 }; certChainParameters.cbSize = sizeof(CERT_CHAIN_PARA); certChainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; @@ -150,7 +151,7 @@ namespace MSIX } static bool GetEnhancedKeyUsage(PCCERT_CONTEXT pCertContext, std::vector& values) - { + { //get OIDS from the extension or property if (pCertContext == NULL) { @@ -258,6 +259,39 @@ namespace MSIX return chainsToTrustedRoot; } + static bool ValidateCertWithWinVerifyTrust(std::vector& buffer) + { + // Set up the structures needed for the WinVerifyTrust call + WINTRUST_BLOB_INFO signatureBlobInfo = { 0 }; + signatureBlobInfo.cbStruct = sizeof(WINTRUST_BLOB_INFO); + // This is exposed here because the consumption API stack requires this information to invoke the P7x SIP. + signatureBlobInfo.gSubject = { 0x5598cff1, 0x68db, 0x4340,{ 0xb5, 0x7f, 0x1c, 0xac, 0xf8, 0x8c, 0x9a, 0x51 } }; + // Set the file Datasize and buffer to validate signature. + signatureBlobInfo.cbMemObject = static_cast(buffer.size()); + signatureBlobInfo.pbMemObject = buffer.data(); + + WINTRUST_DATA trustData = { 0 }; + trustData.cbStruct = sizeof(WINTRUST_DATA); + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + trustData.dwUnionChoice = WTD_CHOICE_BLOB; + trustData.dwStateAction = WTD_STATEACTION_VERIFY; + trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL | WTD_REVOCATION_CHECK_NONE; + trustData.pBlob = &signatureBlobInfo; + + // Verify a file or object using the Authenticode policy provider. + GUID actionId = WINTRUST_ACTION_GENERIC_VERIFY_V2; + HRESULT hr = WinVerifyTrust(static_cast(INVALID_HANDLE_VALUE), &actionId, &trustData); + + // If return value is S_OK the file is trusted. + if (hr == S_OK) + { + return true; + } + + return false; + } + static bool IsAuthenticodeTrustedChain(_In_ PCCERT_CHAIN_CONTEXT certChainContext) { CERT_CHAIN_POLICY_PARA policyParameters = { 0 }; @@ -277,7 +311,7 @@ namespace MSIX bool isAuthenticode = (ERROR_SUCCESS == policyStatus.dwError); policyParameters = { 0 }; - policyParameters.cbSize = sizeof(CERT_CHAIN_POLICY_PARA); + policyParameters.cbSize = sizeof(CERT_CHAIN_POLICY_PARA); policyStatus = {0}; policyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS); ThrowErrorIfNot(Error::SignatureInvalid, @@ -287,7 +321,7 @@ namespace MSIX &policyParameters, &policyStatus), "CertVerifyCertificateChainPolicy failed"); - + bool chainsToTrustedRoot = (ERROR_SUCCESS == policyStatus.dwError); return isAuthenticode && chainsToTrustedRoot; } @@ -300,7 +334,7 @@ namespace MSIX pCertContext->pCertInfo->rgExtension); CERT_BASIC_CONSTRAINTS2_INFO *basicConstraintsT = NULL; - DWORD cbDecoded = 0; + DWORD cbDecoded = 0; if (certExtension && CryptDecodeObjectEx( X509_ASN_ENCODING, X509_BASIC_CONSTRAINTS2, @@ -370,7 +404,7 @@ namespace MSIX { //pkcs7 -- get the end entity PCCERT_CONTEXT pCertContext = NULL; while (NULL != (pCertContext = CertEnumCertificatesInStore(certStoreHandle.get(), pCertContext))) - { + { if (IsCertificateSelfSigned(pCertContext, pCertContext->dwCertEncodingType, 0)) { if (allowSelfSignedCert) @@ -389,7 +423,7 @@ namespace MSIX return NULL; } } - + static bool DoesSignatureCertContainStoreEKU(_In_ byte* rawSignatureBuffer, _In_ ULONG dataSize) { unique_cert_context certificateContext(GetCertContext(rawSignatureBuffer, dataSize, false)); @@ -433,24 +467,26 @@ namespace MSIX { return false; } - int requiredLength = CertNameToStrA( + int requiredLength = CertNameToStrW( X509_ASN_ENCODING, &certificateContext.get()->pCertInfo->Subject, CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG, nullptr, 0); - std::vector publisherT; + std::vector publisherT; publisherT.reserve(requiredLength + 1); - - if (CertNameToStrA( + + if (CertNameToStrW( X509_ASN_ENCODING, &certificateContext.get()->pCertInfo->Subject, CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG, publisherT.data(), requiredLength) > 0) { - publisher = std::string(publisherT.data()); + auto converted = std::wstring_convert>{}.to_bytes(publisherT.data()); + std::string result(converted.begin(), converted.end()); + publisher = result; return true; } return false; @@ -467,9 +503,9 @@ namespace MSIX { // If the caller wants to skip signature validation altogether, just bug out early; we will not read the digests if (option & MSIX_VALIDATION_OPTION_SKIPSIGNATURE) { return false; } - + ThrowErrorIf(Error::MissingAppxSignatureP7X, (nullptr == stream.Get()), "AppxSignature.p7x missing"); - + LARGE_INTEGER li = {0}; ULARGE_INTEGER uli = {0}; ThrowHrIfFailed(stream->Seek(li, StreamBase::Reference::END, &uli)); @@ -523,7 +559,7 @@ namespace MSIX // This first call to CryptMsgGetParam is expected to fail because we don't know // how big of a buffer that it needs to store the inner content - DWORD innerContentTypeSize = 0; + DWORD innerContentTypeSize = 0; ULONG readBytes = 0; ThrowErrorIf(Error::SignatureInvalid, ( !CryptMsgGetParam( @@ -534,7 +570,7 @@ namespace MSIX &innerContentTypeSize) && HRESULT_FROM_WIN32(GetLastError()) != HRESULT_FROM_WIN32(ERROR_MORE_DATA) ), "CryptMsgGetParam failed"); - + // Allocate a temporary buffer std::vector innerContentType(innerContentTypeSize); ThrowErrorIfNot(Error::SignatureInvalid, ( @@ -573,7 +609,7 @@ namespace MSIX innerContent.data(), &innerContentSize) ), "CryptMsgGetParam failed"); - + // Parse the ASN.1 to the the indirect data structure SPC_INDIRECT_DATA_CONTENT* indirectContent = NULL; DWORD indirectContentSize = 0; @@ -596,11 +632,23 @@ namespace MSIX ); origin = MSIX::SignatureOrigin::Unknown; - if (IsStoreOrigin(p7s, p7sSize)) { origin = MSIX::SignatureOrigin::Store; } - else if (IsAuthenticodeOrigin(p7s, p7sSize)) { origin = MSIX::SignatureOrigin::LOB; } + if (IsStoreOrigin(p7s, p7sSize)) + { + origin = MSIX::SignatureOrigin::Store; + } + // Determine whether the signature file is associated with a store cert. + else if (IsAuthenticodeOrigin(p7s, p7sSize)) + { + origin = MSIX::SignatureOrigin::LOB; + } + // WinVerifyTrust is called to validate the certificate signature and the timestamp. + else if (ValidateCertWithWinVerifyTrust(p7x)) + { + origin = MSIX::SignatureOrigin::LOB; + } bool signatureOriginUnknownAllowed = (option & MSIX_VALIDATION_OPTION_ALLOWSIGNATUREORIGINUNKNOWN) == MSIX_VALIDATION_OPTION_ALLOWSIGNATUREORIGINUNKNOWN; - + ThrowErrorIf(Error::CertNotTrusted, ((MSIX::SignatureOrigin::Unknown == origin) && !signatureOriginUnknownAllowed), "Unknown signature origin"); @@ -608,7 +656,7 @@ namespace MSIX ThrowErrorIfNot(Error::SignatureInvalid, GetPublisherDisplayName(p7s, p7sSize, publisher) == true, "Could not retrieve publisher name"); - + return true; } } // namespace MSIX \ No newline at end of file diff --git a/src/msix/PAL/FileSystem/POSIX/DirectoryObject.cpp b/src/msix/PAL/FileSystem/POSIX/DirectoryObject.cpp index d87f3c175..33e2bcf76 100644 --- a/src/msix/PAL/FileSystem/POSIX/DirectoryObject.cpp +++ b/src/msix/PAL/FileSystem/POSIX/DirectoryObject.cpp @@ -13,40 +13,60 @@ #include #include -namespace MSIX { - - template - void WalkDirectory(const std::string& root, Lambda& visitor) +namespace MSIX +{ + namespace { - static std::string dot("."); - static std::string dotdot(".."); - - std::unique_ptr dir(opendir(root.c_str()), closedir); - ThrowErrorIf(Error::FileNotFound, dir.get() == nullptr, "Invalid directory"); - struct dirent* dp; - // TODO: handle junction loops - while((dp = readdir(dir.get())) != nullptr) + template + void WalkDirectory(const std::string& root, Lambda& visitor) { - std::string fileName = std::string(dp->d_name); - std::string child = root + "/" + fileName; - if (dp->d_type == DT_DIR) + static std::string dot("."); + static std::string dotdot(".."); + + std::unique_ptr dir(opendir(root.c_str()), closedir); + ThrowErrorIf(Error::FileNotFound, dir.get() == nullptr, "Invalid directory"); + struct dirent* dp; + // TODO: handle junction loops + while((dp = readdir(dir.get())) != nullptr) { - if ((fileName != dot) && (fileName != dotdot)) + std::string fileName = std::string(dp->d_name); + std::string child = root + "/" + fileName; + if (dp->d_type == DT_DIR) { - WalkDirectory(child, visitor); + if ((fileName != dot) && (fileName != dotdot)) + { + WalkDirectory(child, visitor); + } } - } - else - { - // TODO: ignore .DS_STORE for mac? - struct stat sb; - ThrowErrorIf(Error::Unexpected, stat(child.c_str(), &sb) == -1, std::string("stat call failed " + std::to_string(errno)).c_str()); - if (!visitor(root, std::move(fileName), static_cast(sb.st_mtime))) + else { - break; + // TODO: ignore .DS_STORE for mac? + struct stat sb; + ThrowErrorIf(Error::Unexpected, stat(child.c_str(), &sb) == -1, std::string("stat call failed " + std::to_string(errno)).c_str()); + if (!visitor(root, std::move(fileName), static_cast(sb.st_mtime))) + { + break; + } } } } + + #define DEFAULT_MODE S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH + void mkdirp(std::string& path, size_t startPos = 0, mode_t mode = DEFAULT_MODE) + { + char* p = &path[startPos]; + if (*p == '/') { p++; } + while (*p != '\0') + { + while (*p != '\0' && *p != '/') { p++; } + + char v = *p; + *p = '\0'; + ThrowErrorIfNot(Error::FileCreateDirectory,(mkdir(path.c_str(), mode) != -1 || errno == EEXIST), path.c_str()); + *p = v; + if (*p != '\0') {p++;} + } + } } std::vector DirectoryObject::GetFileNames(FileNameOptions) @@ -55,27 +75,15 @@ namespace MSIX { NOTIMPLEMENTED; } - #define DEFAULT_MODE S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH - void mkdirp(std::string& path, size_t startPos = 0, mode_t mode = DEFAULT_MODE) + char DirectoryObject::GetPathSeparator() const { return '/'; } + + DirectoryObject::DirectoryObject(const std::string& root, bool createRootIfNecessary) : m_root(root) { - char* p = &path[startPos]; - if (*p == '/') { p++; } - while (*p != '\0') + if (!m_root.empty() && m_root.back() == GetPathSeparator()) { - while (*p != '\0' && *p != '/') { p++; } - - char v = *p; - *p = '\0'; - ThrowErrorIfNot(Error::FileCreateDirectory,(mkdir(path.c_str(), mode) != -1 || errno == EEXIST), path.c_str()); - *p = v; - if (*p != '\0') {p++;} + m_root = m_root.substr(0, m_root.length() - 1); } - } - - const char* DirectoryObject::GetPathSeparator() { return "/"; } - DirectoryObject::DirectoryObject(const std::string& root, bool createRootIfNecessary) : m_root(root) - { if (createRootIfNecessary) { mkdirp(m_root); diff --git a/src/msix/PAL/FileSystem/Win32/DirectoryObject.cpp b/src/msix/PAL/FileSystem/Win32/DirectoryObject.cpp index 99a107ea2..5cfcdb8e1 100644 --- a/src/msix/PAL/FileSystem/Win32/DirectoryObject.cpp +++ b/src/msix/PAL/FileSystem/Win32/DirectoryObject.cpp @@ -19,263 +19,277 @@ #include #include -namespace MSIX { - enum class WalkOptions : std::uint16_t +namespace MSIX +{ + namespace { - Files = 1, // Enumerate files within the specified directory - Directories = 2, // Enumerate directories - Recursive = 4 // Enumerate recursively - }; - - inline constexpr WalkOptions operator& (WalkOptions a, WalkOptions b) - { - return static_cast(static_cast(a) & static_cast(b)); - } - - inline constexpr WalkOptions operator| (WalkOptions a, WalkOptions b) - { - return static_cast(static_cast(a) | static_cast(b)); - } - - template - void WalkDirectory(const std::string& root, WalkOptions options, Lambda& visitor) - { - static std::string dot("."); - static std::string dotdot(".."); + enum class WalkOptions : std::uint16_t + { + Files = 1, // Enumerate files within the specified directory + Directories = 2, // Enumerate directories + Recursive = 4 // Enumerate recursively + }; - std::wstring utf16Name = utf8_to_wstring(root); - if ((options & WalkOptions::Files) == WalkOptions::Files) + inline constexpr WalkOptions operator& (WalkOptions a, WalkOptions b) { - utf16Name += L"\\*"; + return static_cast(static_cast(a) & static_cast(b)); } - WIN32_FIND_DATA findFileData = {}; - std::unique_ptr::type, decltype(&::FindClose)> find( - FindFirstFile(reinterpret_cast(utf16Name.c_str()), &findFileData), - &FindClose); + inline constexpr WalkOptions operator| (WalkOptions a, WalkOptions b) + { + return static_cast(static_cast(a) | static_cast(b)); + } - if (INVALID_HANDLE_VALUE == find.get()) + template + void WalkDirectory(const std::string& root, WalkOptions options, Lambda& visitor) { - DWORD lastError = GetLastError(); - if (lastError == ERROR_FILE_NOT_FOUND) + static std::string dot("."); + static std::string dotdot(".."); + + std::wstring utf16Name = utf8_to_wstring(root); + if ((options & WalkOptions::Files) == WalkOptions::Files) { - return; + utf16Name += L"\\*"; } - ThrowWin32ErrorIfNot(lastError, false, "FindFirstFile failed."); - } - // TODO: handle junction loops - do - { - utf16Name = std::wstring(findFileData.cFileName); - auto utf8Name = wstring_to_utf8(utf16Name); - if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + WIN32_FIND_DATA findFileData = {}; + std::unique_ptr::type, decltype(&::FindClose)> find( + FindFirstFile(reinterpret_cast(utf16Name.c_str()), &findFileData), + &FindClose); + + if (INVALID_HANDLE_VALUE == find.get()) { - if (dot != utf8Name && dotdot != utf8Name) + DWORD lastError = GetLastError(); + if (lastError == ERROR_FILE_NOT_FOUND) { - std::string child = root + "\\" + utf8Name; - if ((options & WalkOptions::Directories) == WalkOptions::Directories && - !visitor(root, WalkOptions::Directories, std::move(utf8Name), 0)) - { - break; - } - if ((options & WalkOptions::Recursive) == WalkOptions::Recursive) - { - WalkDirectory(child, options, visitor); - } + return; } + ThrowWin32ErrorIfNot(lastError, false, "FindFirstFile failed."); } - else if ((options & WalkOptions::Files) == WalkOptions::Files) + + // TODO: handle junction loops + do { - ULARGE_INTEGER fileTime; - fileTime.HighPart = findFileData.ftLastWriteTime.dwHighDateTime; - fileTime.LowPart = findFileData.ftLastWriteTime.dwLowDateTime; - if (!visitor(root, WalkOptions::Files, std::move(utf8Name), static_cast(fileTime.QuadPart))) + utf16Name = std::wstring(findFileData.cFileName); + auto utf8Name = wstring_to_utf8(utf16Name); + if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - break; + if (dot != utf8Name && dotdot != utf8Name) + { + std::string child = root + "\\" + utf8Name; + if ((options & WalkOptions::Directories) == WalkOptions::Directories && + !visitor(root, WalkOptions::Directories, std::move(utf8Name), 0)) + { + break; + } + if ((options & WalkOptions::Recursive) == WalkOptions::Recursive) + { + WalkDirectory(child, options, visitor); + } + } + } + else if ((options & WalkOptions::Files) == WalkOptions::Files) + { + ULARGE_INTEGER fileTime; + fileTime.HighPart = findFileData.ftLastWriteTime.dwHighDateTime; + fileTime.LowPart = findFileData.ftLastWriteTime.dwLowDateTime; + if (!visitor(root, WalkOptions::Files, std::move(utf8Name), static_cast(fileTime.QuadPart))) + { + break; + } } } + while (FindNextFile(find.get(), &findFileData)); + + std::uint32_t lastError = static_cast(GetLastError()); + ThrowWin32ErrorIfNot(lastError, + ((lastError == ERROR_NO_MORE_FILES) || + (lastError == ERROR_SUCCESS) || + (lastError == ERROR_ALREADY_EXISTS)), + "FindNextFile"); } - while (FindNextFile(find.get(), &findFileData)); - - std::uint32_t lastError = static_cast(GetLastError()); - ThrowWin32ErrorIfNot(lastError, - ((lastError == ERROR_NO_MORE_FILES) || - (lastError == ERROR_SUCCESS) || - (lastError == ERROR_ALREADY_EXISTS)), - "FindNextFile"); - } - - std::string GetFullPath(const std::string& path) - { - const std::wstring longPathPrefix = LR"(\\?\)"; - - auto pathWide = utf8_to_wstring(path); - std::wstring result = longPathPrefix; - size_t prefixChars = longPathPrefix.size(); - if (pathWide.substr(0, longPathPrefix.size()) == longPathPrefix) + std::string GetFullPath(const std::string& path) { - // Already begins with long path prefix, so don't add it - result = L""; - prefixChars = 0; - } + const std::wstring longPathPrefix = LR"(\\?\)"; - // We aren't going to go out of our way to support crazy incoming paths. - // This means that path here is limited to MAX_PATH, but the resulting path won't be. - DWORD length = GetFullPathNameW(pathWide.c_str(), 0, nullptr, nullptr); + auto pathWide = utf8_to_wstring(path); - // Any errors result in 0 - ThrowLastErrorIf(length == 0, "Failed to get necessary char count for GetFullPathNameW"); + std::wstring result = longPathPrefix; + size_t prefixChars = longPathPrefix.size(); + if (pathWide.substr(0, longPathPrefix.size()) == longPathPrefix) + { + // Already begins with long path prefix, so don't add it + result = L""; + prefixChars = 0; + } - // When requesting size, length accounts for null char - result.resize(prefixChars + length, L' '); + // We aren't going to go out of our way to support crazy incoming paths. + // This means that path here is limited to MAX_PATH, but the resulting path won't be. + DWORD length = GetFullPathNameW(pathWide.c_str(), 0, nullptr, nullptr); - DWORD newLength = GetFullPathNameW(pathWide.c_str(), length, &result[prefixChars], nullptr); + // Any errors result in 0 + ThrowLastErrorIf(length == 0, "Failed to get necessary char count for GetFullPathNameW"); - // On success, newLength does not account for null char - ThrowLastErrorIf(newLength == 0, "Failed to get necessary char count for GetFullPathNameW"); + // When requesting size, length accounts for null char + result.resize(prefixChars + length, L' '); - // The normal scenario is that length - 1 == newLength, but for relative paths, GetFullPathName - // doesn't return the correct case size and there's no guarantee it will be always bigger than needed. - // If we are in a case that length > newlenght there's no harm, just resize the string. Otherwise, it means - // that the first length was not correct and the path didn't get resolved correctly. Try until previous length - // is lower than the next call. - if (length < newLength) - { - DWORD retry = 1; - DWORD previousLength = 0; + DWORD newLength = GetFullPathNameW(pathWide.c_str(), length, &result[prefixChars], nullptr); - do - { - previousLength = newLength + retry; - result.resize(prefixChars + previousLength, L' '); - newLength = GetFullPathNameW(pathWide.c_str(), previousLength, &result[prefixChars], nullptr); - retry++; - } while ((previousLength < newLength) && retry <= 10); - - } + // On success, newLength does not account for null char + ThrowLastErrorIf(newLength == 0, "Failed to get necessary char count for GetFullPathNameW"); - result.resize(prefixChars + newLength); + // The normal scenario is that length - 1 == newLength, but for relative paths, GetFullPathName + // doesn't return the correct case size and there's no guarantee it will be always bigger than needed. + // If we are in a case that length > newlenght there's no harm, just resize the string. Otherwise, it means + // that the first length was not correct and the path didn't get resolved correctly. Try until previous length + // is lower than the next call. + if (length < newLength) + { + DWORD retry = 1; + DWORD previousLength = 0; - return wstring_to_utf8(result); - } + do + { + previousLength = newLength + retry; + result.resize(prefixChars + previousLength, L' '); + newLength = GetFullPathNameW(pathWide.c_str(), previousLength, &result[prefixChars], nullptr); + retry++; + } while ((previousLength < newLength) && retry <= 10); + + } - struct DirectoryInfo - { - std::string Name; - bool Create; + result.resize(prefixChars + newLength); - DirectoryInfo(std::string&& name, bool create) : Name(std::move(name)), Create(create) {} - }; + return wstring_to_utf8(result); + } - static void SplitDirectories(const std::string& path, std::queue& directories, bool forCreate) - { - static char const* const Delims = "\\/"; - const std::string longPathPrefix = R"(\\?\)"; + struct DirectoryInfo + { + std::string Name; + bool Create; - size_t copyPos = 0; - size_t searchPos = 0; - size_t lastPos = 0; + DirectoryInfo(std::string&& name, bool create) : Name(std::move(name)), Create(create) {} + }; - if (path.substr(0, longPathPrefix.size()) == longPathPrefix) + void SplitDirectories(const std::string& path, std::queue& directories, bool forCreate) { - // Absolute path, we need to skip it - searchPos = longPathPrefix.size(); - } + static char const* const Delims = "\\/"; + const std::string longPathPrefix = R"(\\?\)"; - while (lastPos != std::string::npos) - { - lastPos = path.find_first_of(Delims, searchPos); + size_t copyPos = 0; + size_t searchPos = 0; + size_t lastPos = 0; - std::string temp = path.substr(copyPos, lastPos - copyPos); - if (!temp.empty()) + if (path.substr(0, longPathPrefix.size()) == longPathPrefix) { - directories.emplace(std::move(temp), forCreate); + // Absolute path, we need to skip it + searchPos = longPathPrefix.size(); } - copyPos = searchPos = lastPos + 1; - } - } - - // Destroys directories - static void EnsureDirectoryStructureExists(const std::string& root, std::queue& directories, bool lastIsFile, std::string* resultingPath = nullptr) - { - ThrowErrorIf(Error::Unexpected, directories.empty(), "Some path must be given"); + while (lastPos != std::string::npos) + { + lastPos = path.find_first_of(Delims, searchPos); - auto PopFirst = [&directories]() - { - auto result = directories.front(); - directories.pop(); - return result; - }; + std::string temp = path.substr(copyPos, lastPos - copyPos); + if (!temp.empty()) + { + directories.emplace(std::move(temp), forCreate); + } - std::string path = root; - bool isFirst = true; + copyPos = searchPos = lastPos + 1; + } + } - while (!directories.empty()) + // Destroys directories + void EnsureDirectoryStructureExists( + const std::string& root, + std::queue& directories, + bool lastIsFile, + char pathSeparator, + std::string* resultingPath = nullptr) { - auto dirInfo = PopFirst(); - if (!path.empty()) + ThrowErrorIf(Error::Unexpected, directories.empty(), "Some path must be given"); + + auto PopFirst = [&directories]() { - path += DirectoryObject::GetPathSeparator(); - } - path += dirInfo.Name; + auto result = directories.front(); + directories.pop(); + return result; + }; - bool shouldWeCreateDir = dirInfo.Create; + std::string path = root; + bool isFirst = true; - // When the last entry is a file, and we are on the last entry, never create - if (lastIsFile && directories.empty()) + while (!directories.empty()) { - shouldWeCreateDir = false; - } - // If this is a rooted list of directories, the first one will be a device - else if (root.empty() && isFirst) - { - shouldWeCreateDir = false; - } + auto dirInfo = PopFirst(); + if (!path.empty()) + { + path += pathSeparator; + } + path += dirInfo.Name; - if (shouldWeCreateDir) - { - bool found = false; + bool shouldWeCreateDir = dirInfo.Create; - std::wstring utf16Name = utf8_to_wstring(path); - DWORD attr = GetFileAttributesW(utf16Name.c_str()); + // When the last entry is a file, and we are on the last entry, never create + if (lastIsFile && directories.empty()) + { + shouldWeCreateDir = false; + } + // If this is a rooted list of directories, the first one will be a device + else if (root.empty() && isFirst) + { + shouldWeCreateDir = false; + } - if (attr == INVALID_FILE_ATTRIBUTES) + if (shouldWeCreateDir) { - if (!CreateDirectory(utf16Name.c_str(), nullptr)) + bool found = false; + + std::wstring utf16Name = utf8_to_wstring(path); + DWORD attr = GetFileAttributesW(utf16Name.c_str()); + + if (attr == INVALID_FILE_ATTRIBUTES) { - auto lastError = GetLastError(); - ThrowWin32ErrorIfNot(lastError, (lastError == ERROR_ALREADY_EXISTS), std::string("Call to CreateDirectory failed creating: " + path).c_str()); + if (!CreateDirectory(utf16Name.c_str(), nullptr)) + { + auto lastError = GetLastError(); + ThrowWin32ErrorIfNot(lastError, (lastError == ERROR_ALREADY_EXISTS), std::string("Call to CreateDirectory failed creating: " + path).c_str()); + } + } + else + { + ThrowWin32ErrorIfNot(ERROR_ALREADY_EXISTS, attr & FILE_ATTRIBUTE_DIRECTORY, ("A file at this path already exists: " + path).c_str()); } } - else - { - ThrowWin32ErrorIfNot(ERROR_ALREADY_EXISTS, attr & FILE_ATTRIBUTE_DIRECTORY, ("A file at this path already exists: " + path).c_str()); - } - } - isFirst = false; - } + isFirst = false; + } - if (resultingPath) - { - *resultingPath = std::move(path); + if (resultingPath) + { + *resultingPath = std::move(path); + } } } - const char* DirectoryObject::GetPathSeparator() { return "\\"; } + char DirectoryObject::GetPathSeparator() const { return '\\'; } DirectoryObject::DirectoryObject(const std::string& root, bool createRootIfNecessary) { m_root = GetFullPath(root); + if (!m_root.empty() && m_root.back() == GetPathSeparator()) + { + m_root = m_root.substr(0, m_root.length() - 1); + } + if (createRootIfNecessary) { std::queue directories; SplitDirectories(m_root, directories, true); - EnsureDirectoryStructureExists({}, directories, false); + EnsureDirectoryStructureExists({}, directories, false, GetPathSeparator()); } } @@ -296,7 +310,7 @@ namespace MSIX { SplitDirectories(fileName, directories, modeWillCreateFile); std::string path; - EnsureDirectoryStructureExists(m_root, directories, true, &path); + EnsureDirectoryStructureExists(m_root, directories, true, GetPathSeparator(), &path); auto result = ComPtr::Make(std::move(utf8_to_wstring(path)), mode); return result; diff --git a/src/msix/PAL/XML/msxml6/XmlObject.cpp b/src/msix/PAL/XML/msxml6/XmlObject.cpp index b8ceb1a84..01a48305b 100644 --- a/src/msix/PAL/XML/msxml6/XmlObject.cpp +++ b/src/msix/PAL/XML/msxml6/XmlObject.cpp @@ -573,17 +573,21 @@ class MSXMLFactory final : public ComClass ComPtr CreateDomFromStream(XmlContentType footPrintType, const ComPtr& stream) override { + NamespaceManager emptyManager; + #if VALIDATING bool HasIgnorableNamespaces = (XmlContentType::AppxManifestXml == footPrintType); #else bool HasIgnorableNamespaces = false; - NamespaceManager emptyManager; #endif return ComPtr::Make( stream, #if VALIDATING - s_xmlNamespaces[static_cast(footPrintType)], + // No need to validate block map xml schema as it is CPU intensive, especially for large packages with large block map xml, which is + // about 0.1% of the uncompressed payload size. We have semantic validation at consumption to catch mal-formatted xml. + // We consume what we know and ignore what we don't know for future proof. + (XmlContentType::AppxBlockMapXml == footPrintType) ? emptyManager : s_xmlNamespaces[static_cast(footPrintType)], #else emptyManager, #endif diff --git a/src/msix/PAL/XML/xerces-c/XmlObject.cpp b/src/msix/PAL/XML/xerces-c/XmlObject.cpp index 9b0c20309..d85a8d7ea 100644 --- a/src/msix/PAL/XML/xerces-c/XmlObject.cpp +++ b/src/msix/PAL/XML/xerces-c/XmlObject.cpp @@ -76,9 +76,9 @@ class ParsingException final : public XERCES_CPP_NAMESPACE::ErrorHandler void resetErrors() override {} private: std::string GetMessage(const XERCES_CPP_NAMESPACE::SAXParseException& exp) - { - std::u16string utf16FileName = std::u16string(exp.getSystemId()); - std::u16string utf16Message = std::u16string(exp.getMessage()); + { + std::u16string utf16FileName = std::u16string(const_cast(reinterpret_cast(exp.getSystemId()))); + std::u16string utf16Message = std::u16string(const_cast(reinterpret_cast(exp.getMessage()))); return "Error in " + u16string_to_utf8(utf16FileName) + " [Line " + std::to_string(static_cast(exp.getLineNumber())) + ", Col " + std::to_string(static_cast(exp.getColumnNumber())) + "] :: " + u16string_to_utf8(utf16Message); } @@ -92,7 +92,7 @@ class MsixEntityResolver : public XMLEntityResolver InputSource* resolveEntity(XMLResourceIdentifier* resourceIdentifier) { - std::u16string utf16string = std::u16string(resourceIdentifier->getNameSpace()); + std::u16string utf16string = std::u16string(const_cast(reinterpret_cast(resourceIdentifier->getNameSpace()))); std::string id = u16string_to_utf8(utf16string); const auto& entry = std::find(m_namespaces.begin(), m_namespaces.end(), id.c_str()); ThrowErrorIf(MSIX::Error::XmlError, entry == m_namespaces.end(), "Invalid namespace"); @@ -298,7 +298,7 @@ class XercesElement final : public ComClassgetAttribute(nameAttr.Get())); + auto utf16string = std::u16string(const_cast(reinterpret_cast(m_element->getAttribute(nameAttr.Get())))); return u16string_to_utf8(utf16string); } @@ -390,7 +390,7 @@ class XercesDom final : public ComClass std::vector>> schemas; if (footPrintType == XmlContentType::AppxBlockMapXml) { - schemas = GetResources(m_factory, Resource::Type::BlockMap); + // Block map xml does not need schema validation. } else if (footPrintType == XmlContentType::AppxManifestXml) { diff --git a/src/msix/common/AppxFactory.cpp b/src/msix/common/AppxFactory.cpp index 6b068b06a..da4aaebbd 100644 --- a/src/msix/common/AppxFactory.cpp +++ b/src/msix/common/AppxFactory.cpp @@ -10,6 +10,7 @@ #include "MemoryStream.hpp" #include "MsixFeatureSelector.hpp" #include "AppxPackageWriter.hpp" +#include "AppxBundleWriter.hpp" #include "ZipObjectWriter.hpp" #ifdef BUNDLE_SUPPORT @@ -29,7 +30,8 @@ namespace MSIX { // is not smart enough to remove it and the linker will fail. #ifdef MSIX_PACK auto zip = ComPtr::Make(outputStream); - auto result = ComPtr::Make(this, zip); + bool enableFileHash = m_factoryOptions & MSIX_FACTORY_OPTION_WRITER_ENABLE_FILE_HASH; + auto result = ComPtr::Make(this, zip, enableFileHash); *packageWriter = result.Detach(); #endif return static_cast(Error::OK); @@ -84,7 +86,15 @@ namespace MSIX { HRESULT STDMETHODCALLTYPE AppxFactory::CreateBundleWriter(IStream *outputStream, UINT64 bundleVersion, IAppxBundleWriter **bundleWriter) noexcept try { THROW_IF_BUNDLE_NOT_ENABLED - NOTIMPLEMENTED; + ThrowErrorIf(Error::InvalidParameter, (outputStream == nullptr || bundleWriter == nullptr || *bundleWriter != nullptr), "Invalid parameter"); + #ifdef MSIX_PACK + ComPtr self; + ThrowHrIfFailed(QueryInterface(UuidOfImpl::iid, reinterpret_cast(&self))); + auto zip = ComPtr::Make(outputStream); + auto result = ComPtr::Make(self.Get(), zip, bundleVersion); + *bundleWriter = result.Detach(); + #endif + return static_cast(Error::OK); } CATCH_RETURN(); HRESULT STDMETHODCALLTYPE AppxFactory::CreateBundleReader(IStream *inputStream, IAppxBundleReader **bundleReader) noexcept try diff --git a/src/msix/common/AppxManifestObject.cpp b/src/msix/common/AppxManifestObject.cpp index c37a158d7..c4957e5f8 100644 --- a/src/msix/common/AppxManifestObject.cpp +++ b/src/msix/common/AppxManifestObject.cpp @@ -354,10 +354,34 @@ namespace MSIX { return static_cast(Error::OK); } CATCH_RETURN(); - // IAppxManifestReader2 + // IAppxManifestReader2 HRESULT STDMETHODCALLTYPE AppxManifestObject::GetQualifiedResources(IAppxManifestQualifiedResourcesEnumerator **resources) noexcept { - return static_cast(Error::NotImplemented); + ThrowErrorIf(Error::InvalidParameter, (resources == nullptr || *resources != nullptr), "bad pointer"); + + std::vector> qualifiedResources; + struct _context + { + AppxManifestObject* self; + std::vector>* qualifiedResources; + }; + _context context = { this, &qualifiedResources}; + + // Parse Resource elements + XmlVisitor visitorResource(static_cast(&context), [](void* c, const ComPtr& resourceNode)->bool + { + _context* context = reinterpret_cast<_context*>(c); + auto language = resourceNode->GetAttributeValue(XmlAttributeName::Language); + auto scale = resourceNode->GetAttributeValue(XmlAttributeName::Scale); + auto dxFeatureLevel = resourceNode->GetAttributeValue(XmlAttributeName::DXFeatureLevel); + auto resource = ComPtr::Make(context->self->m_factory.Get(), language, scale, dxFeatureLevel); + context->qualifiedResources->push_back(std::move(resource)); + return true; + }); + m_dom->ForEachElementIn(m_dom->GetDocument(), XmlQueryName::Package_Resources_Resource, visitorResource); + *resources = ComPtr:: + Make>(qualifiedResources).Detach(); + return static_cast(Error::OK); } // IAppxManifestReader3 diff --git a/src/msix/common/FileNameValidation.cpp b/src/msix/common/FileNameValidation.cpp index d78286377..2cf9ae94f 100644 --- a/src/msix/common/FileNameValidation.cpp +++ b/src/msix/common/FileNameValidation.cpp @@ -288,10 +288,20 @@ namespace MSIX { return !IsProhibitedFileName(lowSegment) && !HasProhibitedPrefix(lowSegment) && !HasProhibitedSuffix(lowSegment); } - bool FileNameValidation::IsFootPrintFile(const std::string& fileName) + bool FileNameValidation::IsFootPrintFile(const std::string& fileName, bool isBundle) { + bool result = false; std::string lowIdent = Helper::tolower(fileName); - return ((lowIdent == "appxmanifest.xml") || + if (isBundle) + { + result = lowIdent == "appxmetadata/appxbundlemanifest.xml"; + } + else + { + result = lowIdent == "appxmanifest.xml"; + } + + return (result || (lowIdent == "appxsignature.p7x") || (lowIdent == "appxblockmap.xml") || (lowIdent == "[content_types].xml")); diff --git a/src/msix/common/IXml.cpp b/src/msix/common/IXml.cpp index ba22f4b00..5168a56e5 100644 --- a/src/msix/common/IXml.cpp +++ b/src/msix/common/IXml.cpp @@ -28,6 +28,7 @@ static const char* attributeNames[] = { /* Package_Applications_Application_Id */"Id", /* Category */"Category", /* MaxMajorVersionTested */"MaxMajorVersionTested", + /* DXFeatureLevel */"DXFeatureLevel", }; #ifdef USING_MSXML diff --git a/src/msix/msix.cpp b/src/msix/msix.cpp index 47c44eeed..698b2932f 100644 --- a/src/msix/msix.cpp +++ b/src/msix/msix.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "Exceptions.hpp" #include "FileStream.hpp" @@ -17,7 +18,12 @@ #include "AppxPackageObject.hpp" #include "MsixFeatureSelector.hpp" #include "AppxPackageWriter.hpp" +#include "AppxBundleWriter.hpp" #include "ScopeExit.hpp" +#include "VersionHelpers.hpp" +#include "MappingFileParser.hpp" +#include "FileStream.hpp" +#include "MemoryStream.hpp" #include "Signing.hpp" #ifndef WIN32 @@ -42,7 +48,7 @@ static void finalizer(void) { LPVOID STDMETHODCALLTYPE InternalAllocate(SIZE_T cb) { return std::malloc(cb); } void STDMETHODCALLTYPE InternalFree(LPVOID pv) { std::free(pv); } -MSIX_API HRESULT STDMETHODCALLTYPE GetLogTextUTF8(COTASKMEMALLOC* memalloc, char** logText) noexcept try +MSIX_API HRESULT STDMETHODCALLTYPE MsixGetLogTextUTF8(COTASKMEMALLOC* memalloc, char** logText) noexcept try { ThrowErrorIf(MSIX::Error::InvalidParameter, (logText == nullptr || *logText != nullptr), "bad pointer" ); std::size_t countBytes = sizeof(char)*(MSIX::Global::Log::Text().size()+1); @@ -81,28 +87,46 @@ MSIX_API HRESULT STDMETHODCALLTYPE CreateStreamOnFileUTF16( return static_cast(MSIX::Error::OK); } CATCH_RETURN(); -MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactoryWithHeap( +MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactoryWithHeapAndOptions( COTASKMEMALLOC* memalloc, COTASKMEMFREE* memfree, MSIX_VALIDATION_OPTION validationOption, + MSIX_FACTORY_OPTIONS factoryOptions, IAppxFactory** appxFactory) noexcept try { - *appxFactory = MSIX::ComPtr::Make(validationOption, MSIX_APPLICABILITY_OPTION_FULL, memalloc, memfree).Detach(); + *appxFactory = MSIX::ComPtr::Make(validationOption, MSIX_APPLICABILITY_OPTION_FULL, factoryOptions, memalloc, memfree).Detach(); return static_cast(MSIX::Error::OK); } CATCH_RETURN(); +MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactoryWithHeap( + COTASKMEMALLOC* memalloc, + COTASKMEMFREE* memfree, + MSIX_VALIDATION_OPTION validationOption, + IAppxFactory** appxFactory) noexcept try +{ + return CoCreateAppxFactoryWithHeapAndOptions(memalloc, memfree, validationOption, MSIX_FACTORY_OPTION_NONE, appxFactory); +} CATCH_RETURN(); + // Call specific for Windows. Default to call CoTaskMemAlloc and CoTaskMemFree -MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactory( +MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactoryWithOptions( MSIX_VALIDATION_OPTION validationOption, + MSIX_FACTORY_OPTIONS factoryOptions, IAppxFactory** appxFactory) noexcept { #ifdef WIN32 - return CoCreateAppxFactoryWithHeap(CoTaskMemAlloc, CoTaskMemFree, validationOption, appxFactory); + return CoCreateAppxFactoryWithHeapAndOptions(CoTaskMemAlloc, CoTaskMemFree, validationOption, factoryOptions, appxFactory); #else return static_cast(MSIX::Error::NotSupported); #endif } +MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxFactory( + MSIX_VALIDATION_OPTION validationOption, + IAppxFactory** appxFactory) noexcept +{ + return CoCreateAppxFactoryWithOptions(validationOption, MSIX_FACTORY_OPTION_NONE, appxFactory); +} + MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxBundleFactoryWithHeap( COTASKMEMALLOC* memalloc, COTASKMEMFREE* memfree, @@ -111,7 +135,7 @@ MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxBundleFactoryWithHeap( IAppxBundleFactory** appxBundleFactory) noexcept try { THROW_IF_BUNDLE_NOT_ENABLED - *appxBundleFactory = MSIX::ComPtr::Make(validationOption, applicabilityOptions, memalloc, memfree).Detach(); + *appxBundleFactory = MSIX::ComPtr::Make(validationOption, applicabilityOptions, MSIX_FACTORY_OPTION_NONE, memalloc, memfree).Detach(); return static_cast(MSIX::Error::OK); } CATCH_RETURN(); @@ -289,6 +313,206 @@ MSIX_API HRESULT STDMETHODCALLTYPE PackPackage( return static_cast(MSIX::Error::OK); } CATCH_RETURN(); +MSIX_API HRESULT STDMETHODCALLTYPE PackBundle( + MSIX_BUNDLE_OPTIONS bundleOptions, + char* directoryPath, + char* outputBundle, + char* mappingFile, + char* version +) noexcept try +{ + std::uint64_t bundleVersion = 0; + bool flatBundle = false; + bool overWrite = false; + bool manifestOnly = false; + + //Process Common Options + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_VERSION) + { + bundleVersion = MSIX::ConvertVersionStringToUint64(version); + } + + if ((bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_OVERWRITE) && (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_NOOVERWRITE)) + { + ThrowErrorAndLog(MSIX::Error::InvalidParameter, "You can't specify options -o and -no at the same time."); + } + + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_OVERWRITE) + { + overWrite = true; + } + + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_NOOVERWRITE) + { + overWrite = false; + } + + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_BUNDLE_OPTION_FLATBUNDLE) + { + flatBundle = true; + } + + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_BUNDLE_OPTION_BUNDLEMANIFESTONLY) + { + manifestOnly = true; + } + + if (bundleOptions & MSIX_BUNDLE_OPTIONS::MSIX_OPTION_VERBOSE) + { + //TODO: Process option for verbose + } + + //TODO: Error if outputBundle is an existing directory + + //Process Input options + if(directoryPath == nullptr && mappingFile == nullptr) + { + ThrowErrorAndLog(MSIX::Error::InvalidParameter, "You must specify either a content directory (-d) or a mapping file (-f)."); + } + else if(directoryPath != nullptr && mappingFile != nullptr) + { + ThrowErrorAndLog(MSIX::Error::InvalidParameter, "You can't specify both a content directory (-d) and a mapping file (-f)."); + } + //TODO:: Error if directoryPath is a file + + MSIX::MappingFileParser mappingFileParser; + if(mappingFile != nullptr && outputBundle != nullptr) + { + mappingFileParser.ParseMappingFile(mappingFile); + if(!mappingFileParser.IsSectionFound(MSIX::SectionID::FilesSection)) + { + std::ostringstream errorBuilder; + errorBuilder << "The mapping file " << mappingFile << " is missing a [Files] section."; + ThrowErrorAndLog(MSIX::Error::BadFormat, errorBuilder.str().c_str()); + } + } + + auto deleteFile = MSIX::scope_exit([&outputBundle] + { + remove(outputBundle); + }); + + MSIX::ComPtr stream; + std::vector streamVector; + + if(manifestOnly) + { + stream = MSIX::ComPtr::Make(&streamVector); + } + else + { + ThrowHrIfFailed(CreateStreamOnFile(outputBundle, false, &stream)); + } + + MSIX::ComPtr factory; + MSIX_VALIDATION_OPTION validationOptions = MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_FULL; + validationOptions = static_cast(validationOptions | MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_SKIPSIGNATURE); + + ThrowHrIfFailed(CoCreateAppxBundleFactoryWithHeap(InternalAllocate, InternalFree, + validationOptions, + MSIX_APPLICABILITY_OPTIONS::MSIX_APPLICABILITY_OPTION_FULL, + &factory)); + + MSIX::ComPtr bundleWriter; + MSIX::ComPtr bundleWriter4; + + ThrowHrIfFailed(factory->CreateBundleWriter(stream.Get(), bundleVersion, &bundleWriter)); + bundleWriter4 = bundleWriter.As(); + + if(manifestOnly) + { + MSIX::ComPtr appxFactory; + ThrowHrIfFailed(CoCreateAppxFactoryWithHeap(InternalAllocate, InternalFree, + MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_SKIPSIGNATURE, + &appxFactory)); + + std::map fileList = mappingFileParser.GetFileList(); + std::map::iterator fileListIterator; + for (fileListIterator = fileList.begin(); fileListIterator != fileList.end(); fileListIterator++) + { + std::string inputPath = fileListIterator->second; + std::string outputPath = fileListIterator->first; + + std::vector tempPackageVector; + auto tempPackageStream = MSIX::ComPtr::Make(&tempPackageVector); + + auto manifestStream = MSIX::ComPtr::Make(inputPath, MSIX::FileStream::Mode::READ); + + MSIX::ComPtr tempPackageWriter; + ThrowHrIfFailed(appxFactory->CreatePackageWriter(tempPackageStream.Get(), nullptr, &tempPackageWriter)); + ThrowHrIfFailed(tempPackageWriter->Close(manifestStream.Get())); + + LARGE_INTEGER li{0}; + ThrowHrIfFailed(tempPackageStream->Seek(li, MSIX::StreamBase::Reference::START, nullptr)); + + if (flatBundle) + { + ThrowHrIfFailed(bundleWriter4->AddPackageReference(MSIX::utf8_to_wstring(outputPath).c_str(), tempPackageStream.Get(), false)); + } + else + { + ThrowHrIfFailed(bundleWriter4->AddPayloadPackage(MSIX::utf8_to_wstring(outputPath).c_str(), tempPackageStream.Get(), false)); + } + } + } + else + { + if(directoryPath != nullptr && outputBundle != nullptr) + { + auto from = MSIX::ComPtr::Make(directoryPath); + bundleWriter4.As()->ProcessBundlePayload(from, flatBundle); + } + else if(mappingFile != nullptr && outputBundle != nullptr) + { + bundleWriter4.As()->ProcessBundlePayloadFromMappingFile(mappingFileParser.GetFileList(), flatBundle); + } + } + + if(!mappingFileParser.GetExternalPackagesList().empty()) + { + bundleWriter4.As()->ProcessExternalPackages(mappingFileParser.GetExternalPackagesList()); + } + + ThrowHrIfFailed(bundleWriter->Close()); + + if(manifestOnly) + { + LARGE_INTEGER li{0}; + ThrowHrIfFailed(stream->Seek(li, MSIX::StreamBase::Reference::START, nullptr)); + + MSIX_VALIDATION_OPTION validationOption = static_cast(MSIX_VALIDATION_OPTION_SKIPSIGNATURE | MSIX_VALIDATION_OPTION_SKIPPACKAGEVALIDATION); + MSIX_APPLICABILITY_OPTIONS applicabilityOption = static_cast(MSIX_APPLICABILITY_NONE); + + MSIX::ComPtr appxBundleFactory; + ThrowHrIfFailed(CoCreateAppxBundleFactoryWithHeap(InternalAllocate, InternalFree, + validationOption, + applicabilityOption, + &appxBundleFactory)); + + MSIX::ComPtr bundleReader; + ThrowHrIfFailed(appxBundleFactory->CreateBundleReader(stream.Get(), &bundleReader)); + + MSIX::ComPtr bundleManifestReader; + ThrowHrIfFailed(bundleReader->GetManifest(&bundleManifestReader)); + + MSIX::ComPtr bundleManifestStream; + ThrowHrIfFailed(bundleManifestReader->GetStream(&bundleManifestStream)); + + MSIX::ComPtr bundleManifestOutputStream; + ThrowHrIfFailed(CreateStreamOnFile(outputBundle, false, &bundleManifestOutputStream)); + + ULARGE_INTEGER maxSize = { 0 }; + maxSize.QuadPart = UINT64_MAX; + ULARGE_INTEGER sizeRead = { 0 }; + ULARGE_INTEGER sizeWritten = { 0 }; + bundleManifestStream->CopyTo(bundleManifestOutputStream.Get(), maxSize, &sizeRead, &sizeWritten); + } + + deleteFile.release(); + return static_cast(MSIX::Error::OK); + +} CATCH_RETURN(); + MSIX_API HRESULT STDMETHODCALLTYPE SignPackage( MSIX_SIGNING_OPTIONS signingOptions, LPCSTR package, @@ -338,3 +562,4 @@ MSIX_API HRESULT STDMETHODCALLTYPE SignPackage( } CATCH_RETURN(); #endif // MSIX_PACK + diff --git a/src/msix/pack/AppxBlockMapWriter.cpp b/src/msix/pack/AppxBlockMapWriter.cpp index dadd28f9e..7982d8a08 100644 --- a/src/msix/pack/AppxBlockMapWriter.cpp +++ b/src/msix/pack/AppxBlockMapWriter.cpp @@ -5,7 +5,6 @@ #include "XmlWriter.hpp" #include "AppxBlockMapWriter.hpp" -#include "Crypto.hpp" #include "StringHelper.hpp" #include @@ -13,9 +12,13 @@ namespace MSIX { /* - - - + + + + + + @@ -26,7 +29,13 @@ namespace MSIX { static const char* hashMethodAttribute = "HashMethod"; static const char* hashMethodAttributeValue = "http://www.w3.org/2001/04/xmlenc#sha256"; static const char* blockMapNamespace = "http://schemas.microsoft.com/appx/2010/blockmap"; + static const char* blockMapNamespaceV4 = "http://schemas.microsoft.com/appx/2021/blockmap"; + static const char* v4NamespacePrefix = "b4"; + static const char* xmlnsAttributeV4 = "xmlns:b4"; + static const char* ignorableNsAttribute = "IgnorableNamespaces"; + static const char* fileElement = "File"; + static const char* fileHashElementV4 = "b4:FileHash"; static const char* sizeAttribute = "Size"; static const char* nameAttribute = "Name"; static const char* lfhSizeAttribute = "LfhSize"; @@ -36,11 +45,23 @@ namespace MSIX { // BlockMapWriter::BlockMapWriter() : m_xmlWriter(XmlWriter(blockMapElement)) { - // For now, we always use SHA256. m_xmlWriter.AddAttribute(xmlnsAttribute, blockMapNamespace); + + // For now, we always use SHA256. m_xmlWriter.AddAttribute(hashMethodAttribute, hashMethodAttributeValue); } + // Enable full file hash computation and create element to the xml for files larger than DefaultBlockSize (64KB). + // Must be called before first AddFile() is called to added the extra namesapce attribute to the element so the resulted xml will be: + // + void BlockMapWriter::EnableFileHash() + { + m_enableFileHash = true; + m_xmlWriter.AddAttribute(xmlnsAttributeV4, blockMapNamespaceV4); + m_xmlWriter.AddAttribute(ignorableNsAttribute, v4NamespacePrefix); + } + // void BlockMapWriter::AddFile(const std::string& name, std::uint64_t uncompressedSize, std::uint32_t lfh) { @@ -50,6 +71,18 @@ namespace MSIX { m_xmlWriter.AddAttribute(nameAttribute, winName); m_xmlWriter.AddAttribute(sizeAttribute, std::to_string(uncompressedSize)); m_xmlWriter.AddAttribute(lfhSizeAttribute, std::to_string(lfh)); + + if (m_enableFileHash && (uncompressedSize > DefaultBlockSize)) + { + // If the file size is more than a block (64KB), we will add element after all the elements. + // Otherwise, file hash is the same as the block hash, as there is only 1 block in the file. + m_fileHashEngine.Reset(); + m_addFileHash = true; + } + else + { + m_addFileHash = false; + } } // @@ -58,7 +91,7 @@ namespace MSIX { // hash block std::vector hash; ThrowErrorIfNot(MSIX::Error::BlockMapInvalidData, - MSIX::SHA256::ComputeHash(const_cast(block.data()), static_cast(block.size()), hash), + MSIX::SHA256::ComputeHash(block.data(), static_cast(block.size()), hash), "Failed computing hash"); m_xmlWriter.StartElement(blockElement); @@ -70,10 +103,26 @@ namespace MSIX { m_xmlWriter.AddAttribute(sizeAttribute, std::to_string(size)); } m_xmlWriter.CloseElement(); + + if (m_addFileHash) + { + m_fileHashEngine.HashData(block.data(), static_cast(block.size())); + } } void BlockMapWriter::CloseFile() { + if (m_addFileHash) + { + std::vector hash; + m_fileHashEngine.FinalizeAndGetHashValue(hash); + + // + m_xmlWriter.StartElement(fileHashElementV4); + m_xmlWriter.AddAttribute(hashAttribute, Base64::ComputeBase64(hash)); + m_xmlWriter.CloseElement(); + } + m_xmlWriter.CloseElement(); } @@ -82,4 +131,4 @@ namespace MSIX { m_xmlWriter.CloseElement(); ThrowErrorIf(Error::Unexpected, m_xmlWriter.GetState() != XmlWriter::Finish, "The blockmap didn't close correctly"); } -} \ No newline at end of file +} diff --git a/src/msix/pack/AppxBundleWriter.cpp b/src/msix/pack/AppxBundleWriter.cpp new file mode 100644 index 000000000..3d3ecdd51 --- /dev/null +++ b/src/msix/pack/AppxBundleWriter.cpp @@ -0,0 +1,318 @@ +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include "AppxPackaging.hpp" +#include "AppxBundleWriter.hpp" +#include "MsixErrors.hpp" +#include "Exceptions.hpp" +#include "ContentType.hpp" +#include "Encoding.hpp" +#include "AppxFactory.hpp" +#include "ZipObjectWriter.hpp" +#include "AppxManifestObject.hpp" +#include "ScopeExit.hpp" +#include "FileNameValidation.hpp" +#include "StringHelper.hpp" +#include "MemoryStream.hpp" + +#include +#include + +namespace MSIX { + + AppxBundleWriter::AppxBundleWriter(IMsixFactory* factory, const ComPtr& zip, std::uint64_t bundleVersion) + : m_factory(factory), m_zipWriter(zip) + { + m_state = WriterState::Open; + if(bundleVersion == 0) + { + // The generated version number has the format: YYYY.MMDD.hhmm.0 + std::time_t t = std::time(nullptr); + std::tm tm = *std::gmtime(&t); + std::stringstream ss; + ss << std::put_time(&tm, "%Y.%m%d.%H%M.0"); + this->m_bundleWriterHelper.SetBundleVersion(ConvertVersionStringToUint64(ss.str())); + } + else + { + this->m_bundleWriterHelper.SetBundleVersion(bundleVersion); + } + } + + // IBundleWriter + void AppxBundleWriter::ProcessBundlePayload(const ComPtr& from, bool flatBundle) + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + auto fileMap = from->GetFilesByLastModDate(); + for (const auto& file : fileMap) + { + if (!(FileNameValidation::IsFootPrintFile(file.second, true))) + { + std::string ext = Helper::tolower(file.second.substr(file.second.find_last_of(".") + 1)); + auto contentType = ContentType::GetContentTypeByExtension(ext); + auto stream = from.As()->GetFile(file.second); + + if (flatBundle) + { + ThrowHrIfFailed(AddPackageReference(utf8_to_wstring(file.second).c_str(), stream.Get(), false)); + } + } + } + + failState.release(); + } + + void AppxBundleWriter::ProcessBundlePayloadFromMappingFile(std::map fileList, bool flatBundle) + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + std::map::iterator fileListIterator; + for (fileListIterator = fileList.begin(); fileListIterator != fileList.end(); fileListIterator++) + { + std::string inputPath = fileListIterator->second; + std::string outputPath = fileListIterator->first; + + if (!(FileNameValidation::IsFootPrintFile(inputPath, true))) + { + std::string ext = Helper::tolower(inputPath.substr(inputPath.find_last_of(".") + 1)); + auto contentType = ContentType::GetContentTypeByExtension(ext); + auto stream = ComPtr::Make(inputPath, FileStream::Mode::READ); + + if (flatBundle) + { + ThrowHrIfFailed(AddPackageReference(utf8_to_wstring(outputPath).c_str(), stream.Get(), false)); + } + } + } + failState.release(); + } + + void AppxBundleWriter::ProcessExternalPackages(std::map externalPackagesList) + { + std::map::iterator externalPackagesIterator; + for (externalPackagesIterator = externalPackagesList.begin(); externalPackagesIterator != externalPackagesList.end(); externalPackagesIterator++) + { + std::string inputPath = externalPackagesIterator->second; + std::string outputPath = externalPackagesIterator->first; + + if (!(FileNameValidation::IsFootPrintFile(inputPath, true))) + { + auto inputStream = ComPtr::Make(inputPath, FileStream::Mode::READ); + ThrowHrIfFailed(AddExternalPackageReference(utf8_to_wstring(outputPath).c_str(), inputStream.Get(), false)); + } + } + } + + // IAppxBundleWriter + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPayloadPackage(LPCWSTR fileName, IStream* packageStream) noexcept try + { + // TODO: implement + NOTIMPLEMENTED; + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE AppxBundleWriter::Close() noexcept try + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + //Process AppxBundleManifest.xml and add it to the bundle + m_bundleWriterHelper.EndBundleManifest(); + + auto bundleManifestStream = m_bundleWriterHelper.GetBundleManifestStream(); + auto bundleManifestContentType = ContentType::GetBundlePayloadFileContentType(APPX_BUNDLE_FOOTPRINT_FILE_TYPE_MANIFEST); + AddFileToPackage(APPXBUNDLEMANIFEST_XML, bundleManifestStream.Get(), true, true, bundleManifestContentType.c_str()); + + // Close blockmap and add it to the bundle + m_blockMapWriter.Close(); + auto blockMapStream = m_blockMapWriter.GetStream(); + auto blockMapContentType = ContentType::GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE_BLOCKMAP); + AddFileToPackage(APPXBLOCKMAP_XML, blockMapStream.Get(), true, false, blockMapContentType.c_str()); + + // Close content types and add it to the bundle + m_contentTypeWriter.Close(); + auto contentTypeStream = m_contentTypeWriter.GetStream(); + AddFileToPackage(CONTENT_TYPES_XML, contentTypeStream.Get(), true, false, nullptr); + + m_zipWriter->Close(); + failState.release(); + m_state = WriterState::Closed; + return static_cast(Error::OK); + } CATCH_RETURN(); + + // IAppxBundleWriter4 + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPackageReference(LPCWSTR fileName, + IStream* inputStream, BOOL isDefaultApplicablePackage) noexcept try + { + this->AddPackageReferenceInternal(wstring_to_utf8(fileName), inputStream, !!isDefaultApplicablePackage); + return static_cast(Error::OK); + } CATCH_RETURN(); + + void AppxBundleWriter::AddPackageReferenceInternal(std::string fileName, IStream* packageStream, + bool isDefaultApplicablePackage) + { + auto appxFactory = m_factory.As(); + + ComPtr reader; + ThrowHrIfFailed(appxFactory->CreatePackageReader(packageStream, &reader)); + + std::uint64_t packageStreamSize = this->m_bundleWriterHelper.GetStreamSize(packageStream); + + this->m_bundleWriterHelper.AddPackage(fileName, reader.Get(), 0, packageStreamSize, isDefaultApplicablePackage); + } + + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPayloadPackage(LPCWSTR fileName, IStream* packageStream, + BOOL isDefaultApplicablePackage) noexcept try + { + // TODO: implement + NOTIMPLEMENTED; + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddExternalPackageReference(LPCWSTR fileName, + IStream* inputStream, BOOL isDefaultApplicablePackage) noexcept try + { + this->AddExternalPackageReferenceInternal(wstring_to_utf8(fileName), inputStream, !!isDefaultApplicablePackage); + return static_cast(Error::OK); + } CATCH_RETURN(); + + void AppxBundleWriter::AddExternalPackageReferenceInternal(std::string fileName, IStream* packageStream, bool isDefaultApplicablePackage) + { + auto appxFactory = m_factory.As(); + + ComPtr manifestReader; + HRESULT hr = appxFactory->CreateManifestReader(packageStream, &manifestReader); + if(SUCCEEDED(hr)) + { + this->m_bundleWriterHelper.AddExternalPackageReferenceFromManifest(fileName, manifestReader.Get(), isDefaultApplicablePackage); + return; + } + + ComPtr packageReader; + hr = appxFactory->CreatePackageReader(packageStream, &packageReader); + if(SUCCEEDED(hr)) + { + ComPtr manifestReader; + ThrowHrIfFailed(packageReader->GetManifest(&manifestReader)); + this->m_bundleWriterHelper.AddExternalPackageReferenceFromManifest(fileName, manifestReader.Get(), isDefaultApplicablePackage); + return; + } + + ThrowErrorAndLog(Error::InvalidData, "The data is invalid."); + } + + void AppxBundleWriter::ValidateAndAddPayloadFile(const std::string& name, IStream* stream, + APPX_COMPRESSION_OPTION compressionOpt, const char* contentType) + { + ThrowErrorIfNot(Error::InvalidParameter, FileNameValidation::IsFileNameValid(name), "Invalid file name"); + ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsFootPrintFile(name, false), "Trying to add footprint file to package"); + ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsReservedFolder(name), "Trying to add file in reserved folder"); + ValidateCompressionOption(compressionOpt); + AddFileToPackage(name, stream, compressionOpt != APPX_COMPRESSION_OPTION_NONE, true, contentType); + } + + void AppxBundleWriter::AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, + bool addToBlockMap, const char* contentType, bool forceContentTypeOverride) + { + std::string opcFileName; + // Don't encode [Content Type].xml + if (contentType != nullptr) + { + opcFileName = Encoding::EncodeFileName(name); + } + else + { + opcFileName = name; + } + auto fileInfo = m_zipWriter->PrepareToAddFile(opcFileName, toCompress); + + // Add content type to [Content Types].xml + if (contentType != nullptr) + { + m_contentTypeWriter.AddContentType(name, contentType, forceContentTypeOverride); + } + + // This might be called with external IStream implementations. Don't rely on internal implementation of FileStream + LARGE_INTEGER start = { 0 }; + ULARGE_INTEGER end = { 0 }; + ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::END, &end)); + ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::START, nullptr)); + std::uint64_t uncompressedSize = static_cast(end.QuadPart); + + // Add file to block map. + if (addToBlockMap) + { + m_blockMapWriter.AddFile(name, uncompressedSize, fileInfo.first); + } + + auto& zipFileStream = fileInfo.second; + + std::uint64_t bytesToRead = uncompressedSize; + std::uint32_t crc = 0; + while (bytesToRead > 0) + { + // Calculate the size of the next block to add + std::uint32_t blockSize = (bytesToRead > DefaultBlockSize) ? DefaultBlockSize : static_cast(bytesToRead); + bytesToRead -= blockSize; + + // read block from stream + std::vector block; + block.resize(blockSize); + ULONG bytesRead; + ThrowHrIfFailed(stream->Read(static_cast(block.data()), static_cast(blockSize), &bytesRead)); + ThrowErrorIfNot(Error::FileRead, (static_cast(blockSize) == bytesRead), "Read stream file failed"); + crc = crc32(crc, block.data(), static_cast(block.size())); + + // Write block and compress if needed + ULONG bytesWritten = 0; + ThrowHrIfFailed(zipFileStream->Write(block.data(), static_cast(block.size()), &bytesWritten)); + + // Add block to blockmap + if (addToBlockMap) + { + m_blockMapWriter.AddBlock(block, bytesWritten, toCompress); + } + + } + + if (toCompress) + { + // Put the stream termination on + std::vector buffer; + ULONG bytesWritten = 0; + ThrowHrIfFailed(zipFileStream->Write(buffer.data(), static_cast(buffer.size()), &bytesWritten)); + } + + // Close File element + if (addToBlockMap) + { + m_blockMapWriter.CloseFile(); + } + + // This could be the compressed or uncompressed size + auto streamSize = zipFileStream.As()->GetSize(); + m_zipWriter->EndFile(crc, streamSize, uncompressedSize, true); + } + + void AppxBundleWriter::ValidateCompressionOption(APPX_COMPRESSION_OPTION compressionOpt) + { + bool result = ((compressionOpt == APPX_COMPRESSION_OPTION_NONE) || + (compressionOpt == APPX_COMPRESSION_OPTION_NORMAL) || + (compressionOpt == APPX_COMPRESSION_OPTION_MAXIMUM) || + (compressionOpt == APPX_COMPRESSION_OPTION_FAST) || + (compressionOpt == APPX_COMPRESSION_OPTION_SUPERFAST)); + ThrowErrorIfNot(Error::InvalidParameter, result, "Invalid compression option."); + } +} diff --git a/src/msix/pack/AppxPackageWriter.cpp b/src/msix/pack/AppxPackageWriter.cpp index de2d1bfe4..a7a770dea 100644 --- a/src/msix/pack/AppxPackageWriter.cpp +++ b/src/msix/pack/AppxPackageWriter.cpp @@ -23,12 +23,14 @@ #include #include -#include - namespace MSIX { - AppxPackageWriter::AppxPackageWriter(IMsixFactory* factory, const ComPtr& zip) : m_factory(factory), m_zipWriter(zip) + AppxPackageWriter::AppxPackageWriter(IMsixFactory* factory, const ComPtr& zip, bool enableFileHash) : m_factory(factory), m_zipWriter(zip) { + if (enableFileHash) + { + m_blockMapWriter.EnableFileHash(); + } } AppxPackageWriter::AppxPackageWriter(IPackage* packageToSign, std::unique_ptr&& accumulator) : @@ -55,7 +57,7 @@ namespace MSIX { { // If any footprint file is present, ignore it. We only require the AppxManifest.xml // and any other will be ignored and a new one will be created for the package. - if(!(FileNameValidation::IsFootPrintFile(file.second) || FileNameValidation::IsReservedFolder(file.second))) + if(!(FileNameValidation::IsFootPrintFile(file.second, false) || FileNameValidation::IsReservedFolder(file.second))) { std::string ext = Helper::tolower(file.second.substr(file.second.find_last_of(".") + 1)); auto contentType = ContentType::GetContentTypeByExtension(ext); @@ -218,7 +220,7 @@ namespace MSIX { APPX_COMPRESSION_OPTION compressionOpt, const char* contentType) { ThrowErrorIfNot(Error::InvalidParameter, FileNameValidation::IsFileNameValid(name), "Invalid file name"); - ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsFootPrintFile(name), "Trying to add footprint file to package"); + ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsFootPrintFile(name, false), "Trying to add footprint file to package"); ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsReservedFolder(name), "Trying to add file in reserved folder"); ValidateCompressionOption(compressionOpt); AddFileToPackage(name, stream, compressionOpt != APPX_COMPRESSION_OPTION_NONE, true, contentType); diff --git a/src/msix/pack/BundleManifestWriter.cpp b/src/msix/pack/BundleManifestWriter.cpp new file mode 100644 index 000000000..908f3bcfb --- /dev/null +++ b/src/msix/pack/BundleManifestWriter.cpp @@ -0,0 +1,333 @@ +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include "XmlWriter.hpp" +#include "BundleManifestWriter.hpp" +#include "Crypto.hpp" +#include "StringHelper.hpp" +#include "AppxManifestObject.hpp" + +#include + +namespace MSIX { + + static const char* bundleManifestElement = "Bundle"; + static const char* schemaVersionAttribute = "SchemaVersion"; + static const char* Win2019SchemaVersion = "5.0"; + static const char* identityManifestElement = "Identity"; + static const char* nameAttribute = "Name"; + static const char* publisherAttribute = "Publisher"; + static const char* versionAttribute = "Version"; + static const char* packagesManifestElement = "Packages"; + static const char* packageManifestElement = "Package"; + static const char* packageTypeAttribute = "Type"; + static const char* packageArchitectureAttribute = "Architecture"; + static const char* packageResourceIdAttribute = "ResourceId"; + static const char* fileNameAttribute = "FileName"; + static const char* resourcesManifestElement = "Resources"; + static const char* resourceManifestElement = "Resource"; + static const char* resourceLanguageAttribute = "Language"; + static const char* dependenciesManifestElementWithoutPrefix = "Dependencies"; + static const char* targetDeviceFamilyManifestElementWithoutPrefix = "TargetDeviceFamily"; + static const char* tdfMinVersionAttribute = "MinVersion"; + static const char* tdfMaxVersionTestedAttribute = "MaxVersionTested"; + static const char* ApplicationPackageType = "application"; + static const char* ResourcePackageType = "resource"; + static const char* optionalBundleManifestElement = "OptionalBundle"; + + static const char* NamespaceAlias = "b"; + static const char* Namespace = "http://schemas.microsoft.com/appx/2013/bundle"; + static const char* Namespace2016Alias = "b2"; + static const char* Namespace2016 = "http://schemas.microsoft.com/appx/2016/bundle"; + static const char* Namespace2017Alias = "b3"; + static const char* Namespace2017 = "http://schemas.microsoft.com/appx/2017/bundle"; + static const char* Namespace2018Alias = "b4"; + static const char* Namespace2018 = "http://schemas.microsoft.com/appx/2018/bundle"; + static const char* Namespace2019Alias = "b5"; + static const char* Namespace2019 = "http://schemas.microsoft.com/appx/2019/bundle"; + + BundleManifestWriter::BundleManifestWriter() : m_xmlWriter(XmlWriter(bundleManifestElement)) + { + currentState = Uninitialized; + } + + void BundleManifestWriter::StartBundleManifest(std::string targetXmlNamespace, std::string name, + std::string publisher, std::uint64_t version) + { + this->targetXmlNamespace = targetXmlNamespace; + StartBundleElement(); + WriteIdentityElement(name, publisher, version); + StartPackagesElement(); + currentState = BundleManifestStarted; + } + + void BundleManifestWriter::StartBundleElement() + { + m_xmlWriter.AddAttribute(xmlnsAttribute, this->targetXmlNamespace); + m_xmlWriter.AddAttribute(schemaVersionAttribute, Win2019SchemaVersion); + + std::string bundle2018QName = GetQualifiedName(xmlnsAttribute, Namespace2018Alias); + m_xmlWriter.AddAttribute(bundle2018QName, Namespace2018); + + std::string bundle2019QName = GetQualifiedName(xmlnsAttribute, Namespace2019Alias); + m_xmlWriter.AddAttribute(bundle2019QName, Namespace2019); + + std::string ignorableNamespaces; + ignorableNamespaces.append(Namespace2018Alias); + ignorableNamespaces.append(" "); + ignorableNamespaces.append(Namespace2019Alias); + m_xmlWriter.AddAttribute("IgnorableNamespaces", ignorableNamespaces); + } + + void BundleManifestWriter::WriteIdentityElement(std::string name, std::string publisher, std::uint64_t version) + { + m_xmlWriter.StartElement(identityManifestElement); + + m_xmlWriter.AddAttribute(nameAttribute, name); + m_xmlWriter.AddAttribute(publisherAttribute, publisher); + + std::string versionString = MSIX::ConvertVersionToString(version); + m_xmlWriter.AddAttribute(versionAttribute, versionString); + + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::StartPackagesElement() + { + m_xmlWriter.StartElement(packagesManifestElement); + } + + void BundleManifestWriter::AddPackage(PackageInfo packageInfo) + { + WritePackageElement(packageInfo); + currentState = PackagesAdded; + } + + void BundleManifestWriter::WritePackageElement(PackageInfo packageInfo) + { + m_xmlWriter.StartElement(packageManifestElement); + + std::string packageTypeString; + if(packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + packageTypeString = ApplicationPackageType; + } + else if (packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_RESOURCE) + { + packageTypeString = ResourcePackageType; + } + m_xmlWriter.AddAttribute(packageTypeAttribute, packageTypeString); + + std::string versionString = MSIX::ConvertVersionToString(packageInfo.version); + m_xmlWriter.AddAttribute(versionAttribute, versionString); + + if(packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + m_xmlWriter.AddAttribute(packageArchitectureAttribute, packageInfo.architecture); + } + + if (!packageInfo.resourceId.empty() && (packageInfo.resourceId.size() > 0)) + { + m_xmlWriter.AddAttribute(packageResourceIdAttribute, packageInfo.resourceId); + } + + if(!packageInfo.fileName.empty()) + { + m_xmlWriter.AddAttribute(fileNameAttribute, packageInfo.fileName); + } + + if(packageInfo.offset > 0) + { + //TODO: not applicable for flat bundle + } + + if (packageInfo.size > 0 && packageInfo.offset > 0) + { + //TODO: not applicable for flat bundles + } + + //WriteResourcesElement + WriteResourcesElement(packageInfo.resources.Get()); + + //WriteDependenciesElement + WriteDependenciesElement(packageInfo.tdfs.Get()); + + //End Package Tag + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::WriteResourcesElement(IAppxManifestQualifiedResourcesEnumerator* resources) + { + BOOL hasResources = FALSE; + ThrowHrIfFailed(resources->GetHasCurrent(&hasResources)); + + if (hasResources) + { + //Start Resources element + m_xmlWriter.StartElement(resourcesManifestElement); + + BOOL hasNext = FALSE; + ThrowHrIfFailed(resources->GetHasCurrent(&hasNext)); + while (hasNext) + { + ComPtr resource; + ThrowHrIfFailed(resources->GetCurrent(&resource)); + + //Start Resource element + m_xmlWriter.StartElement(resourceManifestElement); + + auto qualifiedResourceInternal = resource.As(); + std::string languageString = qualifiedResourceInternal->GetLanguage(); + if (!languageString.empty()) + { + m_xmlWriter.AddAttribute(resourceLanguageAttribute, languageString); + } + + //TODO:: Write scale and dxfeaturelevel attributes + + //End Resource element + m_xmlWriter.CloseElement(); + + ThrowHrIfFailed(resources->MoveNext(&hasNext)); + } + + //End Resources element + m_xmlWriter.CloseElement(); + } + } + + void BundleManifestWriter::WriteDependenciesElement(IAppxManifestTargetDeviceFamiliesEnumerator* tdfs) + { + BOOL hasNext = FALSE; + ThrowHrIfFailed(tdfs->GetHasCurrent(&hasNext)); + + if (hasNext) + { + std::string dependencyQName = GetElementName(Namespace2018, Namespace2018Alias, dependenciesManifestElementWithoutPrefix); + m_xmlWriter.StartElement(dependencyQName); + + while (hasNext) + { + ComPtr tdf; + ThrowHrIfFailed(tdfs->GetCurrent(&tdf)); + + //Start TargetDeviceFamily manifest element + std::string tdfQName = GetElementName(Namespace2018, Namespace2018Alias, targetDeviceFamilyManifestElementWithoutPrefix); + m_xmlWriter.StartElement(tdfQName); + + auto targetDeviceFamilyInternal = tdf.As(); + std::string name = targetDeviceFamilyInternal->GetName(); + m_xmlWriter.AddAttribute(nameAttribute, name); + + //Get minversion + UINT64 minVersion; + ThrowHrIfFailed(tdf->GetMinVersion(&minVersion)); + std::string minVerionString = MSIX::ConvertVersionToString(minVersion); + m_xmlWriter.AddAttribute(tdfMinVersionAttribute, minVerionString); + + //Get maxversiontested + UINT64 maxVersionTested; + ThrowHrIfFailed(tdf->GetMaxVersionTested(&maxVersionTested)); + std::string maxVersionTestedString = MSIX::ConvertVersionToString(maxVersionTested); + m_xmlWriter.AddAttribute(tdfMaxVersionTestedAttribute, maxVersionTestedString); + + //End TargetDeviceFamily manifest element + m_xmlWriter.CloseElement(); + + ThrowHrIfFailed(tdfs->MoveNext(&hasNext)); + } + + //End Dependencies Tag + m_xmlWriter.CloseElement(); + } + } + + void BundleManifestWriter::AddOptionalBundle(OptionalBundleInfo bundleInfo) + { + EndPackagesElementIfNecessary(); + WriteOptionalBundleElement(bundleInfo); + currentState = OptionalBundlesAdded; + } + + // Writes an OptionalBundle element, which can have one or more Package elements inside. + // + void BundleManifestWriter::WriteOptionalBundleElement(OptionalBundleInfo bundleInfo) + { + ThrowErrorIf(Error::InvalidParameter, bundleInfo.name.empty(), "One or more arguments are invalid"); + ThrowErrorIf(Error::InvalidParameter, bundleInfo.publisher.empty(), "One or more arguments are invalid"); + + m_xmlWriter.StartElement(optionalBundleManifestElement); + m_xmlWriter.AddAttribute(nameAttribute, bundleInfo.name); + m_xmlWriter.AddAttribute(publisherAttribute, bundleInfo.publisher); + + if (bundleInfo.version > 0) + { + std::string versionString = MSIX::ConvertVersionToString(bundleInfo.version); + m_xmlWriter.AddAttribute(versionAttribute, versionString); + } + + if(!bundleInfo.fileName.empty()) + { + m_xmlWriter.AddAttribute(fileNameAttribute, bundleInfo.fileName); + } + + for(size_t i = 0; i < bundleInfo.optionalPackages.size(); i++) + { + WritePackageElement(bundleInfo.optionalPackages[i]); + } + + //End OptionalBundle Tag + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::EndBundleManifest() + { + EndPackagesElementIfNecessary(); + EndBundleElement(); + currentState = BundleManifestEnded; + } + + void BundleManifestWriter::EndPackagesElementIfNecessary() + { + if (currentState == PackagesAdded) + { + EndPackagesElement(); + } + } + + void BundleManifestWriter::EndPackagesElement() + { + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::EndBundleElement() + { + m_xmlWriter.CloseElement(); + } + + std::string BundleManifestWriter::GetElementName(std::string targetNamespace, std::string targetNamespaceAlias, std::string name) + { + std::string qualifiedName; + if ((this->targetXmlNamespace.compare(targetNamespace) != 0) && (!targetNamespaceAlias.empty())) + { + qualifiedName = GetQualifiedName(targetNamespaceAlias, name); + } + else + { + qualifiedName = name; + } + return qualifiedName; + } + + std::string BundleManifestWriter::GetQualifiedName(std::string namespaceAlias, std::string name) + { + std::string output; + output.append(namespaceAlias); + output.append(xmlNamespaceDelimiter); + output.append(name); + return output; + } +} + diff --git a/src/msix/pack/BundleValidationHelper.cpp b/src/msix/pack/BundleValidationHelper.cpp new file mode 100644 index 000000000..3b148ecfb --- /dev/null +++ b/src/msix/pack/BundleValidationHelper.cpp @@ -0,0 +1,249 @@ +#include "BundleValidationHelper.hpp" + +namespace MSIX { + + const std::string Win10DeviceFamilyNameStartsWith = "Windows."; + const std::string NeutralArchString = "neutral"; + const std::string OSVersionRS5 = "10.0.17763.0"; + + BundleValidationHelper::BundleValidationHelper() + {} + + void BundleValidationHelper::ValidateTargetDeviceFamiliesFromManifestPackageId(IAppxManifestPackageIdInternal* packageId, IAppxManifestTargetDeviceFamiliesEnumerator* tdfs, std::string fileName) + { + std::string arch = packageId->GetArchitecture(); + if (arch == NeutralArchString) + { + this->numberOfNeutralAppPackages++; + } + + BOOL hasCurrent = FALSE; + ThrowHrIfFailed(tdfs->GetHasCurrent(&hasCurrent)); + while (hasCurrent) + { + ComPtr tdf; + ThrowHrIfFailed(tdfs->GetCurrent(&tdf)); + + auto tdfInternal = tdf.As(); + std::string tdfName = tdfInternal->GetName(); + + if(!this->bundleTargetsRs5OrLess && StartsWith(tdfName, Win10DeviceFamilyNameStartsWith)) + { + UINT64 minVersion; + ThrowHrIfFailed(tdf->GetMinVersion(&minVersion)); + if (minVersion <= ConvertVersionStringToUint64(OSVersionRS5)) + { + this->bundleTargetsRs5OrLess = true; + } + } + + if(arch == NeutralArchString) + { + PackageNameInfo packageNameInfo; + packageNameInfo.packageFullName = packageId->GetPackageFullName(); + packageNameInfo.fileName = fileName; + + if(!(this->neutralTdfs.insert(std::make_pair(tdfName, packageNameInfo)).second)) + { + std::map::iterator neutralMapIterator = this->neutralTdfs.find(tdfName); + if(neutralMapIterator != this->neutralTdfs.end()) + { + std::string foundFileName = neutralMapIterator->second.fileName; + std::string foundFullName = neutralMapIterator->second.packageFullName; + + std::ostringstream errorBuilder; + errorBuilder << "The package with file name " << packageNameInfo.fileName << " and package full name " << + packageNameInfo.packageFullName << " is not valid in the bundle because the bundle also contains the package with file name " << foundFileName << " and package full name " << foundFullName << " which targets the same device family. Bundles cannot contain multiple neutral app packages with the same target device family value."; + ThrowErrorAndLog(Error::AppxManifestSemanticError, errorBuilder.str().c_str()); + } + } + } + ThrowHrIfFailed(tdfs->MoveNext(&hasCurrent)); + } + } + + bool BundleValidationHelper::StartsWith(std::string str, std::string prefix) + { + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + std::transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower); + return ((prefix.size() <= str.size()) && std::equal(prefix.begin(), prefix.end(), str.begin())); + } + + bool BundleValidationHelper::ContainsMultipleNeutralAppPackages() + { + return (numberOfNeutralAppPackages > 1); + } + + // Checks if an application package has been added to the bundle. + bool BundleValidationHelper::ContainsApplicationPackage() + { + return this->containsApplicationPackage; + } + + void BundleValidationHelper::ValidateContainsMultipleNeutralAppPackages(bool isPre2018BundleManifest) + { + if ((this->numberOfNeutralAppPackages > 1) && (this->bundleTargetsRs5OrLess || isPre2018BundleManifest)) + { + // Pre 19H1 bundles with multiple neutral app packages are invalid. + // Bundles with multiple neutral app packages that use the 19H1 schema are invalid if any of their + // packages targets RS5 or lower. + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The bundle contains at least two conflicting app packages. Bundles that contain packages targeting 10.0.17763.0 or lower cannot contain more than one neutral application package."); + } + } + + // Tests if adding a new package to the bundle is still valid. + // Checks if the new package can be added to the bundle without breaking any + // semantic constraints. AppxManifestSemanticError if its architecture, resource + // ID, or package name conflicts with another package already added to this + // validation helper. + void BundleValidationHelper::AddPackage(APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE type, IAppxManifestPackageIdInternal* packageId, std::string fileName) + { + if (this->numberOfPackagesAdded >= MaxNumberOfPackages) + { + ThrowErrorAndLog(Error::OutOfBounds, "The bundle cannot contain more than 10000 packages."); + } + + PackageNameInfo packageNameInfo; + packageNameInfo.packageFullName = packageId->GetPackageFullName(); + packageNameInfo.fileName = fileName; + + if(!fileNamesMap.empty()) + { + if(!(this->fileNamesMap.insert(std::make_pair(fileName, packageNameInfo)).second)) + { + std::ostringstream errorBuilder; + errorBuilder << "The package with file name " << packageNameInfo.fileName << " and package full name " << packageNameInfo.packageFullName << " is not valid in the bundle because the bundle contains another package with the same file name."; + ThrowErrorAndLog(Error::AppxManifestSemanticError, errorBuilder.str().c_str()); + } + } + + if (type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + this->containsApplicationPackage = true; + } + else if (type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_RESOURCE) + { + ValidateResourcePackage(packageId, packageNameInfo); + } + else + { + // unknown package type + ThrowErrorAndLog(Error::PackagingErrorInternal, "MSIX packaging API has encountered an internal error."); + } + + this->numberOfPackagesAdded++; + } + + void BundleValidationHelper::ValidateResourcePackage(IAppxManifestPackageIdInternal* packageId, PackageNameInfo packageNameInfo) + { + std::string resourceId = packageId->GetResourceId(); + if(resourceId.empty()) + { + // Resource ID is an empty string by default if it's not declared + resourceId = ""; + } + + // The Insert method fails if the entry is already found in the map. + // In that case we have a duplicate resource ID. + if(!(this->resourceIds.insert(std::make_pair(resourceId, packageNameInfo)).second)) + { + std::map::iterator resourceIdMapIterator = this->resourceIds.find(resourceId); + if(resourceIdMapIterator != this->resourceIds.end()) + { + std::string foundFileName = resourceIdMapIterator->second.fileName; + std::string foundFullName = resourceIdMapIterator->second.packageFullName; + + std::ostringstream errorBuilder; + errorBuilder << "The package with file name " << packageNameInfo.fileName << " and package full name " << + packageNameInfo.packageFullName << " is not valid in the bundle because the bundle also contains the package with file name " << foundFileName << " and package full name " << foundFullName << " which has the same resource ID. Bundles cannot contain multiple resource packages with the same resource ID."; + ThrowErrorAndLog(Error::AppxManifestSemanticError, errorBuilder.str().c_str()); + } + } + } + + // Reads the element of a package manifest and determines the type + // (either Application or Resource) of the package. This method will fail with + // AppxManifestSemanticError if the manifest is for a Framework package. + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE BundleValidationHelper::GetPayloadPackageType(IAppxManifestReader* packageManifestReader, std::string fileName) + { + ComPtr packageProperties; + ThrowHrIfFailed(packageManifestReader->GetProperties(&packageProperties)); + + BOOL isFrameworkPackage = FALSE; + ThrowHrIfFailed(packageProperties->GetBoolValue(L"Framework", &isFrameworkPackage)); + + if (isFrameworkPackage) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it is a framework package."); + } + + BOOL isResourcePackage = FALSE; + ThrowHrIfFailed(packageProperties->GetBoolValue(L"ResourcePackage", &isResourcePackage)); + + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType = (isResourcePackage ? APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_RESOURCE : APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION); + return packageType; + } + + // Validates that the package manifest declares OSMinVersion and OSMaxVersionTested values that + // are at least MinimumAllowedOSVersion (6.3). + void BundleValidationHelper::ValidateOSVersion(IAppxManifestReader* packageManifestReader, std::string fileName) + { + ComPtr manifestReader(packageManifestReader); + ComPtr manifestReader3; + ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader3))); + + ComPtr targetDeviceFamilies; + if (SUCCEEDED(manifestReader3->GetTargetDeviceFamilies(&targetDeviceFamilies))) + { + // Manifests that declare target device families are for Threshold and later. + // This meets the OS version requirement + return; + } + else + { + const LPCWSTR ElementsToTest[] = {L"OSMinVersion", L"OSMaxVersionTested"}; + for (int i = 0; i < sizeof(ElementsToTest); i++) + { + UINT64 elementValue = 0; + ThrowHrIfFailed(packageManifestReader->GetPrerequisite(ElementsToTest[i], &elementValue)); + if (elementValue < ConvertVersionStringToUint64(MinimumAllowedOSVersion)) + { + ComPtr packageId; + ThrowHrIfFailed(packageManifestReader->GetPackageId(&packageId)); + auto packageIdInternal = packageId.As(); + std::string packageFullName = packageIdInternal->GetPackageFullName(); + std::ostringstream errorBuilder; + errorBuilder << "The package with file name " << fileName << " and package full name " << packageFullName << " is not valid in the bundle because its manifest declares a value for " + << ElementsToTest[i] << " which is not supported in bundles. The minimum supported value is " << MinimumAllowedOSVersion << "."; + ThrowErrorAndLog(Error::AppxManifestSemanticError, errorBuilder.str().c_str()); + } + } + } + } + + // Validates that the package manifest declares at least one element. + void BundleValidationHelper::ValidateApplicationElement(IAppxManifestReader* packageManifestReader, std::string fileName) + { + ComPtr manifestReader4; + ThrowHrIfFailed(packageManifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader4))); + + ComPtr optionalPackageInfo; + ThrowHrIfFailed(manifestReader4->GetOptionalPackageInfo(&optionalPackageInfo)); + + BOOL packageIsOptional = FALSE; + ThrowHrIfFailed(optionalPackageInfo->GetIsOptionalPackage(&packageIsOptional)); + + if (!packageIsOptional) // optional payload packages are not required to declare any elements + { + ComPtr applications; + ThrowHrIfFailed(packageManifestReader->GetApplications(&applications)); + BOOL hasApplication = FALSE; + ThrowHrIfFailed(applications->GetHasCurrent(&hasApplication)); + + if (!hasApplication) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because its manifest does not declare any Application elements."); + } + } + } +} \ No newline at end of file diff --git a/src/msix/pack/BundleWriterHelper.cpp b/src/msix/pack/BundleWriterHelper.cpp new file mode 100644 index 000000000..598672eac --- /dev/null +++ b/src/msix/pack/BundleWriterHelper.cpp @@ -0,0 +1,305 @@ +#include "BundleWriterHelper.hpp" + +namespace MSIX { + + BundleWriterHelper::BundleWriterHelper() + { + this->bundleVersion = 0; + this->hasExternalPackages = false; + this->hasDefaultOrNeutralResources = false; + } + + std::uint64_t BundleWriterHelper::GetStreamSize(IStream* stream) + { + STATSTG stat; + ThrowHrIfFailed(stream->Stat(&stat, 1)); + + return stat.cbSize.QuadPart; + } + + void BundleWriterHelper::AddPackage(std::string fileName, IAppxPackageReader* packageReader, + std::uint64_t bundleOffset, std::uint64_t packageSize, bool isDefaultApplicableResource) + { + ComPtr packageId; + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType = APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION; + ComPtr resources; + ComPtr tdfs; + + GetValidatedPackageData(fileName, packageReader, &packageType, &packageId, &resources, &tdfs); + + AddValidatedPackageData(fileName, bundleOffset, packageSize, packageType, packageId, + isDefaultApplicableResource, resources.Get(), tdfs.Get()); + } + + void BundleWriterHelper::GetValidatedPackageData( + std::string fileName, + IAppxPackageReader* packageReader, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE* packageType, + IAppxManifestPackageId** packageId, + IAppxManifestQualifiedResourcesEnumerator** resources, + IAppxManifestTargetDeviceFamiliesEnumerator** tdfs) + { + *packageId = nullptr; + *resources = nullptr; + *tdfs = nullptr; + + ComPtr loadedPackageId; + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE loadedPackageType = APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION; + ComPtr loadedResources; + ComPtr loadedTdfs; + + ComPtr manifestReader; + ThrowHrIfFailed(packageReader->GetManifest(&manifestReader)); + ThrowHrIfFailed(manifestReader->GetPackageId(&loadedPackageId)); + + ComPtr manifestReader3; + ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader3))); + ThrowHrIfFailed(manifestReader3->GetQualifiedResources(&loadedResources)); + ThrowHrIfFailed(manifestReader3->GetTargetDeviceFamilies(&loadedTdfs)); + + auto packageIdInternal = loadedPackageId.As(); + + loadedPackageType = this->m_validationHelper.GetPayloadPackageType(manifestReader.Get(), fileName); + this->m_validationHelper.AddPackage(loadedPackageType, packageIdInternal.Get(), fileName); + this->m_validationHelper.ValidateOSVersion(manifestReader.Get(), fileName); + + ValidateNameAndPublisher(packageIdInternal.Get(), fileName); + + if (loadedPackageType == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + this->m_validationHelper.ValidateApplicationElement(manifestReader.Get(), fileName); + if (loadedTdfs.Get() != nullptr) + { + ComPtr tdfCopy; + ThrowHrIfFailed(manifestReader3->GetTargetDeviceFamilies(&tdfCopy)); + this->m_validationHelper.ValidateTargetDeviceFamiliesFromManifestPackageId(packageIdInternal.Get(), tdfCopy.Get(), fileName); + } + } + + *packageType = loadedPackageType; + *packageId = loadedPackageId.Detach(); + *resources = loadedResources.Detach(); + + if (loadedTdfs.Get() != nullptr) + { + *tdfs = loadedTdfs.Detach(); + } + } + + void BundleWriterHelper::ValidateNameAndPublisher(IAppxManifestPackageIdInternal* packageId, + std::string filename) + { + if(this->mainPackageName.empty()) + { + this->mainPackageName = packageId->GetName(); + this->mainPackagePublisher = packageId->GetPublisher(); + } + else + { + std::string packageName = packageId->GetName(); + + if ((this->mainPackageName.compare(packageName)) != 0) + { + std::string packageFullName = packageId->GetPackageFullName(); + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it has a different package family name than other packages in the bundle."); + } + + std::string publisherName = packageId->GetPublisher(); + if ((this->mainPackagePublisher.compare(publisherName)) != 0) + { + std::string packageFullName = packageId->GetPackageFullName(); + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it has a different package family name than other packages in the bundle."); + } + } + } + + void BundleWriterHelper::AddValidatedPackageData( + std::string fileName, + std::uint64_t bundleOffset, + std::uint64_t packageSize, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType, + ComPtr packageId, + bool isDefaultApplicablePackage, + IAppxManifestQualifiedResourcesEnumerator* resources, + IAppxManifestTargetDeviceFamiliesEnumerator* tdfs) + { + auto innerPackageIdInternal = packageId.As(); + + PackageInfo packageInfo; + packageInfo.type = packageType; + packageInfo.architecture = innerPackageIdInternal->GetArchitecture(); + UINT64 version; + ThrowHrIfFailed(packageId->GetVersion(&version)); + packageInfo.version = version; + packageInfo.resourceId = innerPackageIdInternal->GetResourceId(); + packageInfo.isDefaultApplicablePackage = isDefaultApplicablePackage; + packageInfo.resources = resources; + packageInfo.fileName = fileName; + packageInfo.size = packageSize; + packageInfo.offset = bundleOffset; + packageInfo.tdfs = tdfs; + + AddPackageInfoToVector(this->payloadPackages, packageInfo); + } + + void BundleWriterHelper::AddPackageInfoToVector(std::vector& packagesVector, + PackageInfo packageInfo) + { + packagesVector.push_back(packageInfo); + + if (packageInfo.offset == 0) + { + this->hasExternalPackages = true; + } + + if (packageInfo.isDefaultApplicablePackage) + { + this->hasDefaultOrNeutralResources = true; + } + + BOOL hasResources = FALSE; + ThrowHrIfFailed(packageInfo.resources->GetHasCurrent(&hasResources)); + if (!hasResources) + { + this->hasDefaultOrNeutralResources = true; + } + } + + void BundleWriterHelper::AddExternalPackageReferenceFromManifest(std::string fileName, IAppxManifestReader* manifestReader, + bool isDefaultApplicablePackage) + { + ComPtr manifestReader5; + ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader5))); + ComPtr mainPackageDependencies; + ThrowHrIfFailed(manifestReader5->GetMainPackageDependencies(&mainPackageDependencies)); + BOOL hasMoreMainPackageDependencies = FALSE; + ThrowHrIfFailed(mainPackageDependencies->GetHasCurrent(&hasMoreMainPackageDependencies)); + + // Validation: this must be an optional package with a main package dependency with the same + // name and publisher as the actual main packages added to the bundle + ThrowErrorIfNot(Error::AppxManifestSemanticError, hasMoreMainPackageDependencies, "The Appx package's manifest is invalid."); + ComPtr mainPackage; + ThrowHrIfFailed(mainPackageDependencies->GetCurrent(&mainPackage)); + + auto mainPackageInternal = mainPackage.As(); + + if(this->mainPackageName.empty()) + { + this->mainPackageName = mainPackageInternal->GetName(); + this->mainPackagePublisher = mainPackageInternal->GetPublisher(); + } + else + { + std::string packageName = mainPackageInternal->GetName(); + if(packageName.compare(this->mainPackageName) != 0) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The Appx package's manifest is invalid."); + } + + std::string publisher = mainPackageInternal->GetPublisher(); + if(publisher.compare(this->mainPackagePublisher) != 0) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The Appx package's manifest is invalid."); + } + } + + // Validation: this cannot be an optional package with multiple main package dependencies + ThrowHrIfFailed(mainPackageDependencies->MoveNext(&hasMoreMainPackageDependencies)); + ThrowErrorIf(Error::AppxManifestSemanticError, hasMoreMainPackageDependencies, "The Appx package's manifest is invalid."); + + // Populate a new PackageInfo entry with info from the optional package's manifest + ComPtr packageId1; + ThrowHrIfFailed(manifestReader->GetPackageId(&packageId1)); + + auto packageId = packageId1.As(); + + std::string bundleFamilyName = packageId->GetPackageFamilyName(); + std::map::iterator optBundlesIterator = optionalBundles.find(bundleFamilyName); + if(optBundlesIterator == optionalBundles.end()) + { + OptionalBundleInfo newBundleInfo; + newBundleInfo.name = packageId->GetName(); + newBundleInfo.publisher = packageId->GetPublisher(); + newBundleInfo.version = 0; + + optionalBundles.insert(std::pair(bundleFamilyName, newBundleInfo)); + optBundlesIterator = optionalBundles.find(bundleFamilyName); + } + + PackageInfo packageInfo; + packageInfo.type = this->m_validationHelper.GetPayloadPackageType(manifestReader, fileName); + this->m_validationHelper.AddPackage(packageInfo.type, packageId.Get(), fileName); + if (packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + this->m_validationHelper.ValidateApplicationElement(manifestReader, fileName); + } + + packageInfo.architecture = packageId->GetArchitecture(); + UINT64 version; + ThrowHrIfFailed(packageId1->GetVersion(&version)); + packageInfo.version = version; + packageInfo.resourceId = packageId->GetResourceId(); + + ComPtr manifestReader2; + ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader2))); + + packageInfo.isDefaultApplicablePackage = isDefaultApplicablePackage; + ThrowHrIfFailed(manifestReader2->GetQualifiedResources(&packageInfo.resources)); + packageInfo.fileName = fileName; + packageInfo.offset = 0; + packageInfo.size = 0; + + ComPtr manifestReader3; + ThrowHrIfFailed(manifestReader2->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader3))); + manifestReader3->GetTargetDeviceFamilies(&packageInfo.tdfs); + + AddPackageInfoToVector(optBundlesIterator->second.optionalPackages, packageInfo); + } + + void BundleWriterHelper::EndBundleManifest() + { + // A bundle must contain at least one app package. It's an error to Close + // the writer without having added one. + bool result = this->m_validationHelper.ContainsApplicationPackage(); + if (!result) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The bundle must contain at least one app package targeting a known processor architecture."); + } + + std::string targetXmlNamespace = "http://schemas.microsoft.com/appx/2013/bundle"; + bool isPre2018BundleManifest = true; + + // Only use new 2018 bundle schema if the bundle contains more than 1 neutral app packages + if (this->m_validationHelper.ContainsMultipleNeutralAppPackages()) + { + targetXmlNamespace = "http://schemas.microsoft.com/appx/2018/bundle"; + isPre2018BundleManifest = false; + } + else if (this->hasDefaultOrNeutralResources) + { + targetXmlNamespace = "http://schemas.microsoft.com/appx/2017/bundle"; + } + else if ((this->optionalBundles.size() > 0) || this->hasExternalPackages) + { + targetXmlNamespace = "http://schemas.microsoft.com/appx/2016/bundle"; + } + + this->m_validationHelper.ValidateContainsMultipleNeutralAppPackages(isPre2018BundleManifest); + m_bundleManifestWriter.StartBundleManifest(targetXmlNamespace, this->mainPackageName, + this->mainPackagePublisher, this->bundleVersion); + + for(std::size_t i = 0; i < this->payloadPackages.size(); i++) + { + m_bundleManifestWriter.AddPackage(payloadPackages[i]); + } + + std::map::iterator optionalBundleIterator = optionalBundles.begin(); + while(optionalBundleIterator != optionalBundles.end()) + { + m_bundleManifestWriter.AddOptionalBundle(optionalBundleIterator->second); + optionalBundleIterator++; + } + + m_bundleManifestWriter.EndBundleManifest(); + } +} \ No newline at end of file diff --git a/src/msix/pack/ContentType.cpp b/src/msix/pack/ContentType.cpp index e70a5bb8e..105d97613 100644 --- a/src/msix/pack/ContentType.cpp +++ b/src/msix/pack/ContentType.cpp @@ -136,4 +136,22 @@ namespace MSIX { ThrowErrorAndLog(Error::NotSupported, "Payload file content type not found"); } + + const std::string ContentType::GetBundlePayloadFileContentType(APPX_BUNDLE_FOOTPRINT_FILE_TYPE footprintFile) + { + if (footprintFile == APPX_BUNDLE_FOOTPRINT_FILE_TYPE_MANIFEST) + { + return "application/vnd.ms-appx.bundlemanifest+xml"; + } + if (footprintFile == APPX_BUNDLE_FOOTPRINT_FILE_TYPE_BLOCKMAP) + { + return "application/vnd.ms-appx.blockmap+xml"; + } + if (footprintFile == APPX_BUNDLE_FOOTPRINT_FILE_TYPE_SIGNATURE) + { + return "application/vnd.ms-appx.signature"; + } + // TODO: add other ones if needed, otherwise throw + ThrowErrorAndLog(Error::NotSupported, "Bundle Payload file content type not found"); + } } diff --git a/src/msix/pack/ContentTypeWriter.cpp b/src/msix/pack/ContentTypeWriter.cpp index a3ddbe1f0..619498c1b 100644 --- a/src/msix/pack/ContentTypeWriter.cpp +++ b/src/msix/pack/ContentTypeWriter.cpp @@ -67,17 +67,24 @@ namespace MSIX { auto percentageEncodedName = Encoding::EncodeFileName(name); + auto filename = percentageEncodedName; + auto lastSlash = filename.find_last_of("/"); + if (lastSlash != std::string::npos) + { + filename = filename.substr(lastSlash + 1); + } + if (forceOverride) { AddOverride(percentageEncodedName, contentType); return; } - auto findLastPeriod = percentageEncodedName.find_last_of("."); + auto findLastPeriod = filename.find_last_of("."); if (findLastPeriod != std::string::npos) { // See if already exist - std::string ext = percentageEncodedName.substr(percentageEncodedName.find_last_of(".") + 1); + std::string ext = filename.substr(findLastPeriod + 1); std::string normalizedExt = ext; std::transform(normalizedExt.begin(), normalizedExt.end(), normalizedExt.begin(), ::tolower); auto find = m_defaultExtensions.find(normalizedExt); diff --git a/src/msix/pack/MappingFileParser.cpp b/src/msix/pack/MappingFileParser.cpp new file mode 100644 index 000000000..2cf83faa7 --- /dev/null +++ b/src/msix/pack/MappingFileParser.cpp @@ -0,0 +1,283 @@ +#include "MappingFileParser.hpp" +#include +#include +#include + +namespace MSIX { + + const std::string WHITESPACE = " \t"; + + MappingFileParser::MappingFileParser() { + this->lineNumber = 0; + this->errorMessage = ""; + } + + void MappingFileParser::ParseMappingFile(std::string mappingFile) + { + std::ifstream mappingFileStream(mappingFile); + if(mappingFileStream.is_open()) + { + std::string currentLine; + HandlerState state = Continue; + while ((state != Stop) && (state != Fail)) + { + if(std::getline(mappingFileStream, currentLine)) + { + this->lineNumber++; + const char* firstChar = SkipWhitespace(currentLine.c_str()); + + switch(*firstChar) + { + case '[': + state = ParseSectionHeading(firstChar); + break; + + case '"': + if (state != SkipSection) + { + state = ParseMapping(currentLine, firstChar); + } + break; + + case '\0': // line is empty + break; + + default: + std::ostringstream errorBuilder; + errorBuilder << "Error: The mapping file can't be parsed. At line " << this->lineNumber << ": Expecting '[' or '\"'."; + ThrowErrorAndLog(Error::BadFormat, errorBuilder.str().c_str()); + } + } + else // reached end of file + { + state = Stop; + } + } + + if (state == Fail) + { + std::ostringstream errorBuilder; + errorBuilder << "Error: The mapping file can't be parsed. At line: " << this->lineNumber << this->errorMessage; + ThrowErrorAndLog(Error::BadFormat, errorBuilder.str().c_str()); + } + + mappingFileStream.close(); + } + else + { + std::ostringstream errorBuilder; + errorBuilder << "Error: The system cannot find the file specified: " << mappingFile <<"."; + ThrowErrorAndLog(Error::FileNotFound, errorBuilder.str().c_str()); + } + } + + HandlerState MappingFileParser::ParseSectionHeading(std::string line) + { + line = RemoveTrailingWhitespace(line); + char lastChar = line.back(); + if(lastChar == ']') + { + std::string sectionName = std::string(&line[1], &line[line.length()-1]); + return HandleSection(sectionName); + } + else + { + std::ostringstream errorBuilder; + errorBuilder << ": Expecting ']'."; + this->errorMessage = errorBuilder.str().c_str(); + return Fail; + } + } + + HandlerState MappingFileParser::HandleSection(std::string sectionName) + { + this->currentSectionId = UnknownSection; + SectionID sectionId = GetSectionIDByName(sectionName); + if(sectionId != UnknownSection) + { + if (this->IsSectionFound(sectionId)) + { + std::ostringstream errorBuilder; + errorBuilder << " Duplicate " << sectionName << " section found on line " << this->lineNumber << "."; + this->errorMessage = errorBuilder.str().c_str(); + return Fail; + } + else + { + this->foundSection[sectionId] = true; + this->currentSectionId = sectionId; + return Continue; + } + } + return SkipSection; + } + + SectionID MappingFileParser::GetSectionIDByName(std::string sectionName) + { + for (auto sectionMapIterator = KnownSectionNamesMap.begin(); sectionMapIterator != KnownSectionNamesMap.end(); sectionMapIterator++) + { + if(sectionName.compare(sectionMapIterator->first) == 0) + { + return sectionMapIterator->second; + } + } + return UnknownSection; + } + + HandlerState MappingFileParser::ParseMapping(std::string line, const char* firstChar) + { + std::vector pathTokens; + const char* currentChar = firstChar; + + while (*currentChar != '\0') + { + if (*currentChar == '"') + { + std::pair pathValues = SearchString(currentChar + 1, '\"'); + currentChar = pathValues.second; + + if (*currentChar == '\0') + { + std::ostringstream errorBuilder; + errorBuilder << ": Expecting '\"'."; + this->errorMessage = errorBuilder.str().c_str(); + return Fail; + } + + currentChar++; + pathTokens.push_back(pathValues.first); + } + else + { + std::ostringstream errorBuilder; + errorBuilder << ": Expecting '\"'."; + this->errorMessage = errorBuilder.str().c_str(); + return Fail; + } + + const char* nextToken = SkipWhitespace(currentChar); + if ((nextToken == currentChar) && (*nextToken != '\0')) + { + std::ostringstream errorBuilder; + errorBuilder << ": Expecting whitespace after '\"'."; + this->errorMessage = errorBuilder.str().c_str(); + return Fail; + } + currentChar = nextToken; + } + return HandleMapping(pathTokens); + } + + bool MappingFileParser::IsWhitespace(char c) + { + return ((c == L' ') || (c == L'\t')); + } + + const char* MappingFileParser::SkipWhitespace(const char* line) + { + const char* firstChar = line; + while (IsWhitespace(*firstChar)) + { + firstChar++; + } + return firstChar; + } + + std::pair MappingFileParser::SearchString(const char* line, char c) + { + std::string path; + const char* result = line; + while ((*result != c) && (*result != '\0')) + { + path += *result; + result++; + } + return std::make_pair(path, result); + } + + HandlerState MappingFileParser::HandleMapping(std::vector pathTokens) + { + if(pathTokens.size() == 2) + { + std::string inputPath = pathTokens.at(0); + std::string outputPath = pathTokens.at(1); + + if(outputPath.empty() || inputPath.empty()) + { + return Fail; + } + + //Check if file exists + std::ifstream inputFileStream(inputPath); + if(!inputFileStream.is_open()) + { + std::ostringstream errorBuilder; + errorBuilder << ": The system cannot find the file specified: " << inputPath; + this->errorMessage = errorBuilder.str().c_str(); + return Fail; + } + + if(this->currentSectionId == FilesSection) + { + std::map::iterator it; + it = this->list.find(outputPath); + + if(it != this->list.end()) + { + // Adding the same file multiple times with the same key and value is allowed. + if(inputPath.compare(it->second) != 0) + { + std::ostringstream errorBuilder; + errorBuilder << ": You can't add both \"" << inputPath << "\" and \"" << it->second << "\" to the output file as \"" << outputPath << "\"."; + this->errorMessage = errorBuilder.str().c_str(); + return Fail; + } + } + this->list.insert(std::pair(outputPath, inputPath)); + } + else if(this->currentSectionId == ExternalPackagesSection) + { + std::map::iterator it; + it = this->externalPackagesList.find(outputPath); + + if(it != this->externalPackagesList.end()) + { + std::ostringstream errorBuilder; + errorBuilder << ": You can't add both \"" << inputPath << "\" and \"" << it->second << "\" to the output file as \"" << outputPath << "\"."; + this->errorMessage = errorBuilder.str().c_str(); + return Fail; + } + + this->externalPackagesList.insert(std::pair(outputPath, inputPath)); + } + return Continue; + } + else + { + if(pathTokens.size() < 2) + { + std::ostringstream errorBuilder; + errorBuilder << " Output file path is missing on line " << this->lineNumber << "."; + this->errorMessage = errorBuilder.str().c_str(); + } + else + { + std::ostringstream errorBuilder; + errorBuilder << " Too many tokens on line " << this->lineNumber << "."; + this->errorMessage = errorBuilder.str().c_str(); + } + return Fail; + } + } + + bool MappingFileParser::IsSectionFound(SectionID sectionId) + { + return this->foundSection[sectionId]; + } + + std::string MappingFileParser::RemoveTrailingWhitespace(std::string line) + { + size_t end = line.find_last_not_of(WHITESPACE); + return (end == std::string::npos) ? "" : line.substr(0, end + 1); + } +} \ No newline at end of file diff --git a/src/msix/pack/Signing.cpp b/src/msix/pack/Signing.cpp index cc9414f13..9c86d7264 100644 --- a/src/msix/pack/Signing.cpp +++ b/src/msix/pack/Signing.cpp @@ -141,12 +141,12 @@ ComPtr SignatureAccumulator::GetSignatureObject(IZipWriter* // This leaves only the full package hash and the central directory hash. // Simply copy the zip hash over - result->GetFileRecordsDigest() = GetZipHasher().Get(); + GetZipHasher().FinalizeAndGetHashValue(result->GetFileRecordsDigest()); // Create the central directory as it currently exists and hash it ComPtr cdHash = ComPtr::Make(); zipWriter->WriteCentralDirectoryToStream(cdHash.Get()); - result->GetCentralDirectoryDigest() = cdHash->GetHash(); + cdHash->FinalizeAndGetHashValue(result->GetCentralDirectoryDigest()); return result; } @@ -182,11 +182,11 @@ SignatureAccumulator::FileAccumulator::~FileAccumulator() { if (isBlockmap) { - signatureAccumulator.GetSignatureObject()->GetAppxBlockMapDigest() = GetRawHasher().Get(); + GetRawHasher().FinalizeAndGetHashValue(signatureAccumulator.GetSignatureObject()->GetAppxBlockMapDigest()); } else if (isContentTypes) { - signatureAccumulator.GetSignatureObject()->GetContentTypesDigest() = GetRawHasher().Get(); + GetRawHasher().FinalizeAndGetHashValue(signatureAccumulator.GetSignatureObject()->GetContentTypesDigest()); } else { @@ -205,7 +205,7 @@ bool SignatureAccumulator::FileAccumulator::AccumulateRaw(IStream* stream) for (const auto& bytes : Helper::StreamProcessor{ stream, 1 << 20 }) { - hasher.Add(bytes); + hasher.HashData(bytes); } } else @@ -226,7 +226,7 @@ bool SignatureAccumulator::FileAccumulator::AccumulateRaw(const std::vectorWrite(static_cast(bytes.data()), static_cast(bytes.size()), nullptr)); diff --git a/src/msix/pack/VersionHelpers.cpp b/src/msix/pack/VersionHelpers.cpp new file mode 100644 index 000000000..16523ddb2 --- /dev/null +++ b/src/msix/pack/VersionHelpers.cpp @@ -0,0 +1,34 @@ +#include "VersionHelpers.hpp" + +namespace MSIX{ + + std::uint64_t ConvertVersionStringToUint64(const std::string& versionString) + { + std::uint64_t version = 0; + size_t position = 0; + auto nextPeriod = versionString.find('.', position); + version = (std::uint64_t)std::stoi(versionString.substr(position, nextPeriod)) << 0x30; + + position = nextPeriod + 1; + nextPeriod = versionString.find('.', position); + version += (std::uint64_t)std::stoi(versionString.substr(position, nextPeriod)) << 0x20; + + position = nextPeriod + 1; + nextPeriod = versionString.find('.', position); + version += (std::uint64_t)std::stoi(versionString.substr(position, nextPeriod)) << 0x10; + + position = nextPeriod + 1; + nextPeriod = versionString.find('.', position); + version += (std::uint64_t)std::stoi(versionString.substr(position, nextPeriod)); + + return version; + } + + std::string ConvertVersionToString(std::uint64_t version) + { + return std::to_string((version >> 0x30) & 0xFFFF) + "." + + std::to_string((version >> 0x20) & 0xFFFF) + "." + + std::to_string((version >> 0x10) & 0xFFFF) + "." + + std::to_string((version) & 0xFFFF); + } +} diff --git a/src/msix/unpack/AppxPackageObject.cpp b/src/msix/unpack/AppxPackageObject.cpp index 4832b27d3..033c7528e 100644 --- a/src/msix/unpack/AppxPackageObject.cpp +++ b/src/msix/unpack/AppxPackageObject.cpp @@ -221,100 +221,103 @@ namespace MSIX { applicability.InitializeLanguages(); } - for (const auto& package : bundleInfo->GetPackages()) + if (!(validation & MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_SKIPPACKAGEVALIDATION)) { - auto bundleInfoInternal = package.As(); - auto packageName = bundleInfoInternal->GetFileName(); - auto packageStream = m_container->GetFile(Encoding::EncodeFileName(packageName)); - - if (packageStream) - { // The package is in the bundle. Verify is not compressed. - auto zipStream = packageStream.As(); - ThrowErrorIf(Error::AppxManifestSemanticError, zipStream->IsCompressed(), "Packages cannot be compressed"); - } - else if (!packageStream && (bundleInfoInternal->GetOffset() == 0)) // This is a flat bundle. + for (const auto& package : bundleInfo->GetPackages()) { - // We should only do this for flat bundles. If we do it for normal bundles and the user specify a - // stream factory we will basically unpack any package the user wants with the same name as the package - // we are looking, which sounds dangerous. - ComPtr streamFactoryUnk; - ThrowHrIfFailed(factoryOverrides->GetCurrentSpecifiedExtension(MSIX_FACTORY_EXTENSION_STREAM_FACTORY, &streamFactoryUnk)); - - if(streamFactoryUnk.Get() != nullptr) + auto bundleInfoInternal = package.As(); + auto packageName = bundleInfoInternal->GetFileName(); + auto packageStream = m_container->GetFile(Encoding::EncodeFileName(packageName)); + + if (packageStream) + { // The package is in the bundle. Verify is not compressed. + auto zipStream = packageStream.As(); + ThrowErrorIf(Error::AppxManifestSemanticError, zipStream->IsCompressed(), "Packages cannot be compressed"); + } + else if (!packageStream && (bundleInfoInternal->GetOffset() == 0)) // This is a flat bundle. { - auto streamFactory = streamFactoryUnk.As(); - ThrowHrIfFailed(streamFactory->CreateStreamOnRelativePathUtf8(packageName.c_str(), &packageStream)); + // We should only do this for flat bundles. If we do it for normal bundles and the user specify a + // stream factory we will basically unpack any package the user wants with the same name as the package + // we are looking, which sounds dangerous. + ComPtr streamFactoryUnk; + ThrowHrIfFailed(factoryOverrides->GetCurrentSpecifiedExtension(MSIX_FACTORY_EXTENSION_STREAM_FACTORY, &streamFactoryUnk)); + + if(streamFactoryUnk.Get() != nullptr) + { + auto streamFactory = streamFactoryUnk.As(); + ThrowHrIfFailed(streamFactory->CreateStreamOnRelativePathUtf8(packageName.c_str(), &packageStream)); + } + else + { // User didn't specify a stream factory implementation. Assume packages are in the same location + // as the bundle. + auto containerName = GetFileName(); + #ifdef WIN32 + auto lastSeparator = containerName.find_last_of('\\'); + #else + auto lastSeparator = containerName.find_last_of('/'); + #endif + auto expandedPackageName = containerName.substr(0, lastSeparator + 1) + packageName; + ThrowHrIfFailed(CreateStreamOnFile(const_cast(expandedPackageName.c_str()), true, &packageStream)); + } + ThrowErrorIfNot(Error::FileNotFound, packageStream, "Package from a flat bundle is not present"); } else - { // User didn't specify a stream factory implementation. Assume packages are in the same location - // as the bundle. - auto containerName = GetFileName(); - #ifdef WIN32 - auto lastSeparator = containerName.find_last_of('\\'); - #else - auto lastSeparator = containerName.find_last_of('/'); - #endif - auto expandedPackageName = containerName.substr(0, lastSeparator + 1) + packageName; - ThrowHrIfFailed(CreateStreamOnFile(const_cast(expandedPackageName.c_str()), true, &packageStream)); + { + ThrowErrorIfNot(Error::FileNotFound, packageStream, "Package is not in container"); } - ThrowErrorIfNot(Error::FileNotFound, packageStream, "Package from a flat bundle is not present"); - } - else - { - ThrowErrorIfNot(Error::FileNotFound, packageStream, "Package is not in container"); - } - // Semantic checks - LARGE_INTEGER start = { 0 }; - ULARGE_INTEGER end = { 0 }; - ThrowHrIfFailed(packageStream->Seek(start, StreamBase::Reference::END, &end)); - ThrowHrIfFailed(packageStream->Seek(start, StreamBase::Reference::START, nullptr)); - - UINT64 size; - ThrowHrIfFailed(package->GetSize(&size)); - ThrowErrorIf(Error::AppxManifestSemanticError, end.u.LowPart != size, - "Size mistmach of package between AppxManifestBundle.appx and container"); - - // Validate the package - ComPtr reader; - ThrowHrIfFailed(appxFactory->CreatePackageReader(packageStream.Get(), &reader)); - ComPtr innerPackageManifest; - ThrowHrIfFailed(reader->GetManifest(&innerPackageManifest)); - // Do semantic checks to validate the relationship between the AppxBundleManifest and the AppxManifest. - ComPtr bundlePackageId; - ThrowHrIfFailed(package->GetPackageId(&bundlePackageId)); - auto bundlePackageIdInternal = bundlePackageId.As(); - - ComPtr innerPackageId; - ThrowHrIfFailed(innerPackageManifest->GetPackageId(&innerPackageId)); - auto innerPackageIdInternal = innerPackageId.As(); - ThrowErrorIf(Error::AppxManifestSemanticError, - (innerPackageIdInternal->GetPublisher() != bundlePackageIdInternal->GetPublisher()), - "AppxBundleManifest.xml and AppxManifest.xml publisher mismatch"); - UINT64 bundlePackageVersion = 0; - UINT64 innerPackageVersion = 0; - ThrowHrIfFailed(bundlePackageId->GetVersion(&bundlePackageVersion)); - ThrowHrIfFailed(innerPackageId->GetVersion(&innerPackageVersion)); - ThrowErrorIf(Error::AppxManifestSemanticError, - (innerPackageVersion != bundlePackageVersion), - "AppxBundleManifest.xml and AppxManifest.xml version mismatch"); - ThrowErrorIf(Error::AppxManifestSemanticError, - (innerPackageIdInternal->GetName() != bundlePackageIdInternal->GetName()), - "AppxBundleManifest.xml and AppxManifest.xml name mismatch"); - ThrowErrorIf(Error::AppxManifestSemanticError, - (innerPackageIdInternal->GetArchitecture() != bundlePackageIdInternal->GetArchitecture()) && - !(innerPackageIdInternal->GetArchitecture().empty() && (bundlePackageIdInternal->GetArchitecture() == "neutral")), - "AppxBundleManifest.xml and AppxManifest.xml architecture mismatch"); - - APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType; - ThrowHrIfFailed(package->GetPackageType(&packageType)); - - // Validation is done, now see if the package is applicable. - applicability.AddPackageIfApplicable(reader, packageType, package); - - m_files[packageName] = ComPtr::Make(m_factory.Get(), packageName, std::move(packageStream)); - // Intentionally don't remove from fileToProcess. For bundles, it is possible to don't unpack packages, like - // resource packages that are not languages packages. + // Semantic checks + LARGE_INTEGER start = { 0 }; + ULARGE_INTEGER end = { 0 }; + ThrowHrIfFailed(packageStream->Seek(start, StreamBase::Reference::END, &end)); + ThrowHrIfFailed(packageStream->Seek(start, StreamBase::Reference::START, nullptr)); + + UINT64 size; + ThrowHrIfFailed(package->GetSize(&size)); + ThrowErrorIf(Error::AppxManifestSemanticError, end.u.LowPart != size, + "Size mistmach of package between AppxManifestBundle.appx and container"); + + // Validate the package + ComPtr reader; + ThrowHrIfFailed(appxFactory->CreatePackageReader(packageStream.Get(), &reader)); + ComPtr innerPackageManifest; + ThrowHrIfFailed(reader->GetManifest(&innerPackageManifest)); + // Do semantic checks to validate the relationship between the AppxBundleManifest and the AppxManifest. + ComPtr bundlePackageId; + ThrowHrIfFailed(package->GetPackageId(&bundlePackageId)); + auto bundlePackageIdInternal = bundlePackageId.As(); + + ComPtr innerPackageId; + ThrowHrIfFailed(innerPackageManifest->GetPackageId(&innerPackageId)); + auto innerPackageIdInternal = innerPackageId.As(); + ThrowErrorIf(Error::AppxManifestSemanticError, + (innerPackageIdInternal->GetPublisher() != bundlePackageIdInternal->GetPublisher()), + "AppxBundleManifest.xml and AppxManifest.xml publisher mismatch"); + UINT64 bundlePackageVersion = 0; + UINT64 innerPackageVersion = 0; + ThrowHrIfFailed(bundlePackageId->GetVersion(&bundlePackageVersion)); + ThrowHrIfFailed(innerPackageId->GetVersion(&innerPackageVersion)); + ThrowErrorIf(Error::AppxManifestSemanticError, + (innerPackageVersion != bundlePackageVersion), + "AppxBundleManifest.xml and AppxManifest.xml version mismatch"); + ThrowErrorIf(Error::AppxManifestSemanticError, + (innerPackageIdInternal->GetName() != bundlePackageIdInternal->GetName()), + "AppxBundleManifest.xml and AppxManifest.xml name mismatch"); + ThrowErrorIf(Error::AppxManifestSemanticError, + (innerPackageIdInternal->GetArchitecture() != bundlePackageIdInternal->GetArchitecture()) && + !(innerPackageIdInternal->GetArchitecture().empty() && (bundlePackageIdInternal->GetArchitecture() == "neutral")), + "AppxBundleManifest.xml and AppxManifest.xml architecture mismatch"); + + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType; + ThrowHrIfFailed(package->GetPackageType(&packageType)); + + // Validation is done, now see if the package is applicable. + applicability.AddPackageIfApplicable(reader, packageType, package); + + m_files[packageName] = ComPtr::Make(m_factory.Get(), packageName, std::move(packageStream)); + // Intentionally don't remove from fileToProcess. For bundles, it is possible to don't unpack packages, like + // resource packages that are not languages packages. + } } applicability.GetApplicablePackages(&m_applicablePackages, &m_applicablePackagesNames); diff --git a/src/test/msixtest/api_packagewriter.cpp b/src/test/msixtest/api_packagewriter.cpp index 6f7fb91fa..5c6441fe6 100644 --- a/src/test/msixtest/api_packagewriter.cpp +++ b/src/test/msixtest/api_packagewriter.cpp @@ -16,44 +16,31 @@ using namespace MsixTest::Pack; constexpr std::uint32_t DefaultBlockSize = 65536; -void InitializePackageWriter(IStream* outputStream, IAppxPackageWriter** packageWriter) +void InitializePackageWriter(IStream* outputStream, IAppxPackageWriter** packageWriter, bool enableFileHash = false) { *packageWriter = nullptr; MsixTest::ComPtr appxFactory; - REQUIRE_SUCCEEDED(CoCreateAppxFactoryWithHeap(MsixTest::Allocators::Allocate, MsixTest::Allocators::Free, - MSIX_VALIDATION_OPTION_SKIPSIGNATURE, &appxFactory)); + if (enableFileHash) + { + REQUIRE_SUCCEEDED(CoCreateAppxFactoryWithHeapAndOptions(MsixTest::Allocators::Allocate, MsixTest::Allocators::Free, + MSIX_VALIDATION_OPTION_SKIPSIGNATURE, MSIX_FACTORY_OPTION_WRITER_ENABLE_FILE_HASH, &appxFactory)); + } + else + { + REQUIRE_SUCCEEDED(CoCreateAppxFactoryWithHeap(MsixTest::Allocators::Allocate, MsixTest::Allocators::Free, + MSIX_VALIDATION_OPTION_SKIPSIGNATURE, &appxFactory)); + } REQUIRE_SUCCEEDED(appxFactory->CreatePackageWriter(outputStream, nullptr, packageWriter)); return; } -// Test creating instances of package writer -TEST_CASE("Api_AppxPackageWriter_create", "[api]") +void TestAppxPackageWriter_good(LPCSTR outputFileName, bool enableFileHash) { - MsixTest::ComPtr appxFactory; - REQUIRE_SUCCEEDED(CoCreateAppxFactoryWithHeap(MsixTest::Allocators::Allocate, MsixTest::Allocators::Free, - MSIX_VALIDATION_OPTION_SKIPSIGNATURE, &appxFactory)); - - auto outputStream = MsixTest::StreamFile("test_package.msix", false, true); - MsixTest::ComPtr packageWriter; - - // Test incorrect nullptr parameter. Note, currently we don't support APPX_PACKAGE_SETTINGS in the MSIX SDK - // we always create a zip64 package and use SHA256 as hash method, so nullptr as second parameter is fine. - REQUIRE_HR(static_cast(MSIX::Error::InvalidParameter), - appxFactory->CreatePackageWriter(nullptr, nullptr, &packageWriter)); - REQUIRE_HR(static_cast(MSIX::Error::InvalidParameter), - appxFactory->CreatePackageWriter(outputStream.Get(), nullptr, nullptr)); - - REQUIRE_SUCCEEDED(appxFactory->CreatePackageWriter(outputStream.Get(), nullptr, &packageWriter)); -} - -// Test creating a valid msix package via IAppxPackageWriter with different file sizes -TEST_CASE("Api_AppxPackageWriter_good", "[api]") -{ - auto outputStream = MsixTest::StreamFile("test_package.msix", false, true); + auto outputStream = MsixTest::StreamFile(outputFileName, false, true); MsixTest::ComPtr packageWriter; - InitializePackageWriter(outputStream.Get(), &packageWriter); + InitializePackageWriter(outputStream.Get(), &packageWriter, enableFileHash); // These values are set so that the files added to the package have increasingly // larger sizes, with the first file having a small size < DefaultBlockSize, and @@ -61,7 +48,7 @@ TEST_CASE("Api_AppxPackageWriter_good", "[api]") const std::uint32_t contentSizeIncrement = DefaultBlockSize * 10 / static_cast(TestConstants::GoodFileNames.size()) + 1; std::uint32_t contentSize = 10; - for(const auto& fileName : TestConstants::GoodFileNames) + for (const auto& fileName : TestConstants::GoodFileNames) { // Create file and write random data to it auto fileStream = MsixTest::StreamFile(fileName.first, false, true); @@ -87,6 +74,38 @@ TEST_CASE("Api_AppxPackageWriter_good", "[api]") MsixTest::InitializePackageReader(outputStream.Get(), &packageReader); } +// Test creating instances of package writer +TEST_CASE("Api_AppxPackageWriter_create", "[api]") +{ + MsixTest::ComPtr appxFactory; + REQUIRE_SUCCEEDED(CoCreateAppxFactoryWithHeap(MsixTest::Allocators::Allocate, MsixTest::Allocators::Free, + MSIX_VALIDATION_OPTION_SKIPSIGNATURE, &appxFactory)); + + auto outputStream = MsixTest::StreamFile("test_package.msix", false, true); + MsixTest::ComPtr packageWriter; + + // Test incorrect nullptr parameter. Note, currently we don't support APPX_PACKAGE_SETTINGS in the MSIX SDK + // we always create a zip64 package and use SHA256 as hash method, so nullptr as second parameter is fine. + REQUIRE_HR(static_cast(MSIX::Error::InvalidParameter), + appxFactory->CreatePackageWriter(nullptr, nullptr, &packageWriter)); + REQUIRE_HR(static_cast(MSIX::Error::InvalidParameter), + appxFactory->CreatePackageWriter(outputStream.Get(), nullptr, nullptr)); + + REQUIRE_SUCCEEDED(appxFactory->CreatePackageWriter(outputStream.Get(), nullptr, &packageWriter)); +} + +// Test creating a valid msix package via IAppxPackageWriter with different file sizes +TEST_CASE("Api_AppxPackageWriter_good", "[api]") +{ + TestAppxPackageWriter_good("test_package.msix", false /* enableFileHash */); +} + +// Test creating a valid msix package with file hash enabled in block map via IAppxPackageWriter with different file sizes +TEST_CASE("Api_AppxPackageWriter_FileHashEnabled_good", "[api]") +{ + TestAppxPackageWriter_good("test_package_with_filehash.msix", true /* enableFileHash */); +} + // Test creating a valid msix package via IAppxPackageWriter. // Create a package with empty files in start, middle and end positions, // and reuse the same content streams packaged under different names. diff --git a/src/test/msixtest/pack.cpp b/src/test/msixtest/pack.cpp index e57960577..49b5069b6 100644 --- a/src/test/msixtest/pack.cpp +++ b/src/test/msixtest/pack.cpp @@ -45,6 +45,22 @@ TEST_CASE("Pack_Good", "[pack]") MsixTest::Pack::ValidatePackageStream(outputPackage); } +TEST_CASE("Pack_Good_EndsWithSeparator", "[pack]") +{ + HRESULT expected = S_OK; + +#ifdef WIN32 + std::string directory = "input\\"; +#else + std::string directory = "input/"; +#endif + + RunPackTest(expected, directory); + + // Verify output package + MsixTest::Pack::ValidatePackageStream(outputPackage); +} + // Fail if there's no AppxManifest.xml TEST_CASE("Pack_AppxManifestNotPresent", "[pack]") { diff --git a/src/test/msixtest/unpack.cpp b/src/test/msixtest/unpack.cpp index 257677b98..25310d9ca 100644 --- a/src/test/msixtest/unpack.cpp +++ b/src/test/msixtest/unpack.cpp @@ -373,3 +373,16 @@ TEST_CASE("Unpack_To_Absolute_Path", "[unpack]") // Clean directory CHECK(MsixTest::Directory::CleanDirectory(outputDir)); } + +#ifdef WIN32 +// TODO: verify timestamp in non-windows platforms. +TEST_CASE("Unpack_Validate_Timestamp", "[unpack]") +{ + HRESULT expected = S_OK; + std::string package = "ValidateTimestamp.appx"; + MSIX_VALIDATION_OPTION validation = MSIX_VALIDATION_OPTION_FULL; + MSIX_PACKUNPACK_OPTION packUnpack = MSIX_PACKUNPACK_OPTION_NONE; + + RunUnpackTest(expected, package, validation, packUnpack); +} +#endif diff --git a/src/test/testData/unpack/ValidateTimestamp.appx b/src/test/testData/unpack/ValidateTimestamp.appx new file mode 100644 index 000000000..e5675fef5 Binary files /dev/null and b/src/test/testData/unpack/ValidateTimestamp.appx differ diff --git a/tools/pipelines-tasks/.gitignore b/tools/pipelines-tasks/.gitignore new file mode 100644 index 000000000..5795b5397 --- /dev/null +++ b/tools/pipelines-tasks/.gitignore @@ -0,0 +1,31 @@ +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# VSIX packaged extension +*.vsix + +# Test Outputs +.taskkey +certificate.txt + +# Ignrore all compiled .js and .js.map mappings +*.js +*.js.map + +# Packaged node modules +*.tgz + +# PAT for publishing +pat.txt + +# Powershell modules +ps_modules/ + +# Don't exclude VS Code configuration for this project as it contains debug configuration +!.vscode/ \ No newline at end of file diff --git a/tools/pipelines-tasks/.mocharc.yml b/tools/pipelines-tasks/.mocharc.yml new file mode 100644 index 000000000..090f3d387 --- /dev/null +++ b/tools/pipelines-tasks/.mocharc.yml @@ -0,0 +1,5 @@ +# Test timeout in milliseconds +timeout: 300000 + +# Test duration to flag as slow +slow: 40000 diff --git a/tools/pipelines-tasks/.vscode/launch.json b/tools/pipelines-tasks/.vscode/launch.json new file mode 100644 index 000000000..d0885bc28 --- /dev/null +++ b/tools/pipelines-tasks/.vscode/launch.json @@ -0,0 +1,65 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch AppInstallerFile", + "skipFiles": [ + "/**" + ], + "preLaunchTask": "tsc: build - tsconfig.json", + "program": "${workspaceFolder}/AppInstallerFile/index.js", + "outFiles": [ + "${workspaceFolder}/**/*.js" + ], + "envFile": "${workspaceFolder}/AppInstallerFile/debug_inputs.env" + }, + { + "type": "node", + "request": "launch", + "name": "Launch MsixAppAttach", + "skipFiles": [ + "/**" + ], + "preLaunchTask": "tsc: build - tsconfig.json", + "program": "${workspaceFolder}/MsixAppAttach/index.js", + "outFiles": [ + "${workspaceFolder}/**/*.js" + ], + "envFile": "${workspaceFolder}/MsixAppAttach/debug_inputs.env" + }, + { + "type": "node", + "request": "launch", + "name": "Launch MsixSigning", + "skipFiles": [ + "/**" + ], + "preLaunchTask": "tsc: build - tsconfig.json", + "program": "${workspaceFolder}/MsixSigning/index.js", + "args": ["--mockSecureFileDownload"], + "outFiles": [ + "${workspaceFolder}/**/*.js" + ], + "envFile": "${workspaceFolder}/MsixSigning/debug_inputs.env" + }, + { + "type": "node", + "request": "launch", + "name": "Launch MsixPackaging", + "skipFiles": [ + "/**" + ], + "preLaunchTask": "tsc: build - tsconfig.json", + "program": "${workspaceFolder}/MsixPackaging/index.js", + "outFiles": [ + "${workspaceFolder}/**/*.js" + ], + "envFile": "${workspaceFolder}/MsixPackaging/debug_inputs.env" + } + ] +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/de-DE/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/de-DE/resources.resjson new file mode 100644 index 000000000..d83eca1c4 --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/de-DE/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "App-Installer-Datei für MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Erstellen oder Aktualisieren einer App-Installer-Datei für MSIX-Apps", + "loc.instanceNameFormat": "App-Installer-Datei erstellen", + "loc.input.label.package": "Paket", + "loc.input.help.package": "Pfad zum Paket oder Bundle", + "loc.input.label.outputPath": "Pfad der Ausgabedatei:", + "loc.input.help.outputPath": "Der Pfad des zu schreibenden App-Installers.", + "loc.input.label.method": "Methode zum Erstellen einer App-Installer-Datei", + "loc.input.help.method": "Verwendete Methode zum Erstellen einer App-Installer-Datei.", + "loc.input.label.existingFile": "Pfad zur vorhandenen App-Installer-Datei", + "loc.input.help.existingFile": "Der Pfad der vorhandenen App-Installer-Datei, die aktualisiert werden soll.", + "loc.input.label.versionUpdateMethod": "Methode zum Aktualisieren der Version der App-Installer-Datei", + "loc.input.help.versionUpdateMethod": "Erhöhen Sie das major/minor/build/revision der App-Installer-Datei, oder geben Sie eine zu verwendende Versionsnummer ein.", + "loc.input.label.fileVersion": "Version der App-Installer-Datei", + "loc.input.help.fileVersion": "Die Versionsnummer, die angegeben wird. Muss die Form (major).(minor).(build).(revision) aufweisen.", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "Web-URI auf die umgeleitete App-Installer-Datei.", + "loc.input.label.mainItemUri": "Hauptpaket/Bundle-URI", + "loc.input.help.mainItemUri": "URI zum Speicherort des App-Pakets bzw. des Bundles.", + "loc.input.label.updateOnLaunch": "Update beim Start", + "loc.input.help.updateOnLaunch": "Stellen Sie die App so ein, dass sie beim Start nach Updates sucht.", + "loc.input.label.hoursBetweenUpdateChecks": "Stunden zwischen Update-Überprüfungen", + "loc.input.help.hoursBetweenUpdateChecks": "Die Häufigkeit, mit der das System nach Updates für die App sucht.", + "loc.input.label.showPromptWhenUpdating": "Benutzeroberfläche für Benutzer beim Aktualisieren anzeigen", + "loc.input.help.showPromptWhenUpdating": "Eine Benutzer Oberfläche anzeigen, die den Benutzer darüber informiert, wenn beim Starten der App ein Update auftritt.", + "loc.input.label.updateBlocksActivation": "Update blockiert App-Aktivierung", + "loc.input.help.updateBlocksActivation": "Blockieren Sie den Start der App, bis die Aktualisierung abgeschlossen ist.", + "loc.input.label.addOptionalItem1": "Ein optionales Paket/Bundle hinzufügen", + "loc.input.help.addOptionalItem1": "Ein optionales Paket oder Bundle hinzufügen", + "loc.input.label.optionalItem1Name": "Optionales Element 1: Name", + "loc.input.help.optionalItem1Name": "Der Paket- oder Bundle-Name des ersten optionalen Elements, das aufgenommen werden soll", + "loc.input.label.optionalItem1Publisher": "Optionales Element 1: Herausgeber", + "loc.input.help.optionalItem1Publisher": "Der Name des Herausgebers des ersten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.optionalItem1Version": "Optionales Element 1: Version", + "loc.input.help.optionalItem1Version": "Die Versionsnummer des ersten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.optionalItem1ProcessorArchitecture": "Optionales Element 1: Prozessorarchitektur", + "loc.input.help.optionalItem1ProcessorArchitecture": "Die Prozessorarchitektur des ersten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.optionalItem1URI": "Optionales Element 1: URI", + "loc.input.help.optionalItem1URI": "Der URI des ersten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.addOptionalItem2": "Ein zweites optionales Paket/Bundle hinzufügen", + "loc.input.help.addOptionalItem2": "Ein zweites optionales Paket/Bundle hinzufügen", + "loc.input.label.optionalItem2Name": "Optionales Element 2: Name", + "loc.input.help.optionalItem2Name": "Der Paket- oder Bundle-Name Ihres zweiten optionalen Elements, das aufgenommen werden soll", + "loc.input.label.optionalItem2Publisher": "Optionales Element 2: Herausgeber", + "loc.input.help.optionalItem2Publisher": "Der Name des Herausgebers des zweiten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.optionalItem2Version": "Optionales Element 2: Version", + "loc.input.help.optionalItem2Version": "Die Versionsnummer des zweiten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.optionalItem2ProcessorArchitecture": "Optionales Element 2: Prozessorarchitektur", + "loc.input.help.optionalItem2ProcessorArchitecture": "Die Prozessorarchitektur des zweiten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.optionalItem2URI": "Optionales Element 2: URI", + "loc.input.help.optionalItem2URI": "Der URI des zweiten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.addOptionalItem3": "Ein drittes optionales Paket/Bundle hinzufügen", + "loc.input.help.addOptionalItem3": "Ein drittes optionales Paket/Bundle hinzufügen", + "loc.input.label.optionalItem3Name": "Optionales Element 3: Name", + "loc.input.help.optionalItem3Name": "Der Paket- oder Bundle-Name des dritten optionalen Elements, das Sie aufgenommen werden soll", + "loc.input.label.optionalItem3Publisher": "Optionales Element 3: Herausgeber", + "loc.input.help.optionalItem3Publisher": "Der Name des Herausgebers des dritten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.optionalItem3Version": "Optionales Element 3: Version", + "loc.input.help.optionalItem3Version": "Die Versionsnummer des dritten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.optionalItem3ProcessorArchitecture": "Optionales Element 3: Prozessorarchitektur", + "loc.input.help.optionalItem3ProcessorArchitecture": "Die Prozessorarchitektur des dritten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.optionalItem3URI": "Optionales Element 3: URI", + "loc.input.help.optionalItem3URI": "Der URI des dritten optionalen Elements, das eingeschlossen werden soll.", + "loc.input.label.addDependency1": "Eine Abhängigkeit hinzufügen", + "loc.input.help.addDependency1": "Eine Abhängigkeit hinzufügen.", + "loc.input.label.dependency1Name": "Abhängigkeit 1: Name", + "loc.input.help.dependency1Name": "Der Name der ersten zu einzuschließenden Abhängigkeit.", + "loc.input.label.dependency1Publisher": "Abhängigkeit 1: Herausgeber", + "loc.input.help.dependency1Publisher": "Der Name des Herausgebers der ersten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency1Version": "Abhängigkeit 1: Version", + "loc.input.help.dependency1Version": "Die Versionsnummer der ersten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency1ProcessorArchitecture": "Abhängigkeit 1: Prozessorarchitektur", + "loc.input.help.dependency1ProcessorArchitecture": "Die Prozessorarchitektur der ersten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency1URI": "Abhängigkeit 1: URI", + "loc.input.help.dependency1URI": "Der URI der ersten Abhängigkeit, die einbezogen werden soll.", + "loc.input.label.addDependency2": "Eine Zweite Abhängigkeit hinzufügen.", + "loc.input.help.addDependency2": "Eine zweite Abhängigkeit hinzufügen.", + "loc.input.label.dependency2Name": "Abhängigkeit 2: Name", + "loc.input.help.dependency2Name": "Der Name der zweiten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency2Publisher": "Abhängigkeit 2: Herausgeber", + "loc.input.help.dependency2Publisher": "Der Name des Herausgebers der zweiten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency2Version": "Abhängigkeit 2: Version", + "loc.input.help.dependency2Version": "Die Versionsnummer der zweiten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency2ProcessorArchitecture": "Abhängigkeit 2: Prozessorarchitektur", + "loc.input.help.dependency2ProcessorArchitecture": "Die Prozessorarchitektur der zweiten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency2URI": "Abhängigkeit 2: URI", + "loc.input.help.dependency2URI": "Der URI der zweiten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.addDependency3": "Eine Dritte Abhängigkeit hinzufügen.", + "loc.input.help.addDependency3": "Eine dritte Abhängigkeit hinzufügen.", + "loc.input.label.dependency3Name": "Abhängigkeit 3: Name", + "loc.input.help.dependency3Name": "Der Name der dritten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency3Publisher": "Abhängigkeit 3: Herausgeber", + "loc.input.help.dependency3Publisher": "Der Name des Herausgebers der dritten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency3Version": "Abhängigkeit 3: Version", + "loc.input.help.dependency3Version": "Die Versionsnummer der dritten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency3ProcessorArchitecture": "Abhängigkeit 3: Prozessorarchitektur", + "loc.input.help.dependency3ProcessorArchitecture": "Die Prozessorarchitektur der dritten Abhängigkeit, die eingeschlossen werden soll.", + "loc.input.label.dependency3URI": "Abhängigkeit 3: URI", + "loc.input.help.dependency3URI": "Der URI der dritten Abhängigkeit, die eingeschlossen werden soll." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/en-US/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/en-US/resources.resjson new file mode 100644 index 000000000..e213668df --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/en-US/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "App Installer file for MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Create or update an App Installer file for MSIX apps", + "loc.instanceNameFormat": "Create App Installer file", + "loc.input.label.package": "Package", + "loc.input.help.package": "Path to the package or bundle.", + "loc.input.label.outputPath": "Output File Path", + "loc.input.help.outputPath": "The path of the App Installer to be written.", + "loc.input.label.method": "Method to Create App Installer File", + "loc.input.help.method": "Method used to create the App Installer file.", + "loc.input.label.existingFile": "Path to Existing App Installer File", + "loc.input.help.existingFile": "The path of the existing App Installer file to update.", + "loc.input.label.versionUpdateMethod": "Method to Update the App Installer File's Version", + "loc.input.help.versionUpdateMethod": "Increment the major/minor/build/revision of the App Installer file or enter a version number to be used.", + "loc.input.label.fileVersion": "Version for App Installer file", + "loc.input.help.fileVersion": "The version number which will be given. Must take the form (major).(minor).(build).(revision).", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "Web URI to the redirected App Installer file.", + "loc.input.label.mainItemUri": "Main Package/Bundle URI", + "loc.input.help.mainItemUri": "URI to the app package/bundle location.", + "loc.input.label.updateOnLaunch": "Update On Launch", + "loc.input.help.updateOnLaunch": "Set the app to check for updates when launched.", + "loc.input.label.hoursBetweenUpdateChecks": "Hours Between Update Checks", + "loc.input.help.hoursBetweenUpdateChecks": "How often the system will check for updates to the app.", + "loc.input.label.showPromptWhenUpdating": "Show UI to User when Updating", + "loc.input.help.showPromptWhenUpdating": "Show a UI to notify the user if an update occurs when launching the app.", + "loc.input.label.updateBlocksActivation": "Update Blocks App Activation", + "loc.input.help.updateBlocksActivation": "Block the app from launching until the update finishes.", + "loc.input.label.addOptionalItem1": "Add an Optional Package/Bundle", + "loc.input.help.addOptionalItem1": "Add an optional package or bundle", + "loc.input.label.optionalItem1Name": "Optional Item 1: Name", + "loc.input.help.optionalItem1Name": "The package or bundle name of your first optional item to include.", + "loc.input.label.optionalItem1Publisher": "Optional Item 1: Publisher", + "loc.input.help.optionalItem1Publisher": "The publisher name of the first optional item to include.", + "loc.input.label.optionalItem1Version": "Optional Item 1: Version", + "loc.input.help.optionalItem1Version": "The version number of the first optional item to include.", + "loc.input.label.optionalItem1ProcessorArchitecture": "Optional Item 1: Processor Architecture", + "loc.input.help.optionalItem1ProcessorArchitecture": "The processor architecture of the first optional item to include.", + "loc.input.label.optionalItem1URI": "Optional Item 1: URI", + "loc.input.help.optionalItem1URI": "The URI of the first optional item to include.", + "loc.input.label.addOptionalItem2": "Add a Second Optional Package/Bundle", + "loc.input.help.addOptionalItem2": "Add a second optional package or bundle", + "loc.input.label.optionalItem2Name": "Optional Item 2: Name", + "loc.input.help.optionalItem2Name": "The package or bundle name of your second optional item to include.", + "loc.input.label.optionalItem2Publisher": "Optional Item 2: Publisher", + "loc.input.help.optionalItem2Publisher": "The publisher name of the second optional item to include.", + "loc.input.label.optionalItem2Version": "Optional Item 2: Version", + "loc.input.help.optionalItem2Version": "The version number of the second optional item to include.", + "loc.input.label.optionalItem2ProcessorArchitecture": "Optional Item 2: Processor Architecture", + "loc.input.help.optionalItem2ProcessorArchitecture": "The processor architecture of the second optional item to include.", + "loc.input.label.optionalItem2URI": "Optional Item 2: URI", + "loc.input.help.optionalItem2URI": "The URI of the second optional item to include.", + "loc.input.label.addOptionalItem3": "Add a Third Optional Package/Bundle", + "loc.input.help.addOptionalItem3": "Add a third optional package or bundle", + "loc.input.label.optionalItem3Name": "Optional Item 3: Name", + "loc.input.help.optionalItem3Name": "The package or bundle name of your third optional item to include.", + "loc.input.label.optionalItem3Publisher": "Optional Item 3: Publisher", + "loc.input.help.optionalItem3Publisher": "The publisher name of the third optional item to include.", + "loc.input.label.optionalItem3Version": "Optional Item 3: Version", + "loc.input.help.optionalItem3Version": "The version number of the third optional item to include.", + "loc.input.label.optionalItem3ProcessorArchitecture": "Optional Item 3: Processor Architecture", + "loc.input.help.optionalItem3ProcessorArchitecture": "The processor architecture of the third optional item to include.", + "loc.input.label.optionalItem3URI": "Optional Item 3: URI", + "loc.input.help.optionalItem3URI": "The URI of the third optional item to include.", + "loc.input.label.addDependency1": "Add a Dependency", + "loc.input.help.addDependency1": "Add a dependency.", + "loc.input.label.dependency1Name": "Dependency 1: Name", + "loc.input.help.dependency1Name": "The name of the first dependency to include.", + "loc.input.label.dependency1Publisher": "Dependency 1: Publisher", + "loc.input.help.dependency1Publisher": "The publisher name of the first dependency to include.", + "loc.input.label.dependency1Version": "Dependency 1: Version", + "loc.input.help.dependency1Version": "The version number of the first dependency to include.", + "loc.input.label.dependency1ProcessorArchitecture": "Dependency 1: Processor Architecture", + "loc.input.help.dependency1ProcessorArchitecture": "The processor architecture of the first dependency to include.", + "loc.input.label.dependency1URI": "Dependency 1: URI", + "loc.input.help.dependency1URI": "The URI of the first dependency to include.", + "loc.input.label.addDependency2": "Add a Second Dependency", + "loc.input.help.addDependency2": "Add a second dependency.", + "loc.input.label.dependency2Name": "Dependency 2: Name", + "loc.input.help.dependency2Name": "The name of the second dependency to include.", + "loc.input.label.dependency2Publisher": "Dependency 2: Publisher", + "loc.input.help.dependency2Publisher": "The publisher name of the second dependency to include.", + "loc.input.label.dependency2Version": "Dependency 2: Version", + "loc.input.help.dependency2Version": "The version number of the second dependency to include.", + "loc.input.label.dependency2ProcessorArchitecture": "Dependency 2: Processor Architecture", + "loc.input.help.dependency2ProcessorArchitecture": "The processor architecture of the second dependency to include.", + "loc.input.label.dependency2URI": "Dependency 2: URI", + "loc.input.help.dependency2URI": "The URI of the second dependency to include.", + "loc.input.label.addDependency3": "Add a Third Dependency", + "loc.input.help.addDependency3": "Add a third dependency.", + "loc.input.label.dependency3Name": "Dependency 3: Name", + "loc.input.help.dependency3Name": "The name of the third dependency to include.", + "loc.input.label.dependency3Publisher": "Dependency 3: Publisher", + "loc.input.help.dependency3Publisher": "The publisher name of the third dependency to include.", + "loc.input.label.dependency3Version": "Dependency 3: Version", + "loc.input.help.dependency3Version": "The version number of the third dependency to include.", + "loc.input.label.dependency3ProcessorArchitecture": "Dependency 3: Processor Architecture", + "loc.input.help.dependency3ProcessorArchitecture": "The processor architecture of the third dependency to include.", + "loc.input.label.dependency3URI": "Dependency 3: URI", + "loc.input.help.dependency3URI": "The URI of the third dependency to include." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/es-ES/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/es-ES/resources.resjson new file mode 100644 index 000000000..882606e32 --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/es-ES/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "Archivo instalador de aplicaciones para MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Crear o actualizar un archivo instalador de aplicación para aplicaciones de MSIX", + "loc.instanceNameFormat": "Crear el archivo del instalador de la aplicación", + "loc.input.label.package": "Paquete", + "loc.input.help.package": "La ruta de acceso al paquete o empaquetado.", + "loc.input.label.outputPath": "Ruta de acceso del archivo de salida", + "loc.input.help.outputPath": "Ruta de acceso del instalador de la aplicación que se va a escribir.", + "loc.input.label.method": "Método para crear el archivo instalador de la aplicación", + "loc.input.help.method": "Método usado para crear el archivo de instalación de la aplicación.", + "loc.input.label.existingFile": "Ruta de acceso al archivo del instalador de la aplicación existente", + "loc.input.help.existingFile": "Ruta de acceso del archivo de instalador de aplicaciones existente que se va a actualizar.", + "loc.input.label.versionUpdateMethod": "Método para actualizar la versión del archivo del instalador de la aplicación", + "loc.input.help.versionUpdateMethod": "Aumente el número de versión principal/secundaria/compilación/revisión del archivo instalador de la aplicación, o bien escriba el número de versión que se usará.", + "loc.input.label.fileVersion": "Versión para el archivo del instalador de la aplicación", + "loc.input.help.fileVersion": "El número de versión que se proporcionará. Debe tener el formato (principal). (menor). (compilación). (revisión).", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "URI web para el archivo redireccionado del Instalador de aplicaciones.", + "loc.input.label.mainItemUri": "Paquete principal/URI empaquetado", + "loc.input.help.mainItemUri": "URI en la ubicación de paquetes/empaquetados de la aplicación", + "loc.input.label.updateOnLaunch": "Actualizar al inicio", + "loc.input.help.updateOnLaunch": "Establecer la aplicación para comprobar si hay actualizaciones al iniciarlas.", + "loc.input.label.hoursBetweenUpdateChecks": "Horas entre comprobaciones de actualización", + "loc.input.help.hoursBetweenUpdateChecks": "Frecuencia con la que el sistema comprobará si hay actualizaciones de la aplicación.", + "loc.input.label.showPromptWhenUpdating": "Mostrar la UI al usuario al actualizar", + "loc.input.help.showPromptWhenUpdating": "Mostrar una interfaz de usuario para notificar al usuario si se realiza una actualización al iniciar la aplicación.", + "loc.input.label.updateBlocksActivation": "La actualización bloquea la activación de la aplicación", + "loc.input.help.updateBlocksActivation": "Impide que la aplicación se inicie hasta que finalice la actualización.", + "loc.input.label.addOptionalItem1": "Agregar un paquete/empaquetado optativo", + "loc.input.help.addOptionalItem1": "Agregar un paquete o empaquetado optativo", + "loc.input.label.optionalItem1Name": "Elemento opcional 1: nombre", + "loc.input.help.optionalItem1Name": "El nombre de paquete o empaquetado del primer elemento opcional para incluir.", + "loc.input.label.optionalItem1Publisher": "Elemento opcional 1: publicador", + "loc.input.help.optionalItem1Publisher": "Nombre del editor del primer elemento opcional que se va a incluir.", + "loc.input.label.optionalItem1Version": "Elemento opcional 1: versión", + "loc.input.help.optionalItem1Version": "El número de versión del primer elemento opcional que se va a incluir.", + "loc.input.label.optionalItem1ProcessorArchitecture": "Elemento opcional 1: arquitectura del procesador", + "loc.input.help.optionalItem1ProcessorArchitecture": "Arquitectura de procesador del primer elemento opcional que se va a incluir.", + "loc.input.label.optionalItem1URI": "Elemento opcional 1: URI", + "loc.input.help.optionalItem1URI": "URI del primer elemento opcional que se va a incluir.", + "loc.input.label.addOptionalItem2": "Agregar un segundo paquete o empaquetado optativo", + "loc.input.help.addOptionalItem2": "Agregar un segundo paquete opcional o empaquetado", + "loc.input.label.optionalItem2Name": "Elemento opcional 2: nombre", + "loc.input.help.optionalItem2Name": "El nombre de paquete o empaquetado de su segundo elemento opcional para incluir.", + "loc.input.label.optionalItem2Publisher": "Elemento opcional 2: publicador", + "loc.input.help.optionalItem2Publisher": "Nombre del editor del segundo elemento opcional que se va a incluir.", + "loc.input.label.optionalItem2Version": "Elemento opcional 2: versión", + "loc.input.help.optionalItem2Version": "El número de versión del segundo elemento opcional que se va a incluir.", + "loc.input.label.optionalItem2ProcessorArchitecture": "Elemento opcional 2: arquitectura del procesador", + "loc.input.help.optionalItem2ProcessorArchitecture": "La arquitectura de procesador del segundo elemento opcional que se va a incluir.", + "loc.input.label.optionalItem2URI": "Elemento opcional 2: URI", + "loc.input.help.optionalItem2URI": "URI del segundo elemento opcional que se va a incluir.", + "loc.input.label.addOptionalItem3": "Agregar un tercer paquete/empaquetado optativo", + "loc.input.help.addOptionalItem3": "Agregar un tercer paquete o empaquetado optativo", + "loc.input.label.optionalItem3Name": "Elemento opcional 3: nombre", + "loc.input.help.optionalItem3Name": "El nombre de paquete o empaquetado de su tercer elemento opcional para incluir.", + "loc.input.label.optionalItem3Publisher": "Elemento opcional 3: publicador", + "loc.input.help.optionalItem3Publisher": "Nombre del editor del tercer elemento opcional que se va a incluir.", + "loc.input.label.optionalItem3Version": "Elemento opcional 3: versión", + "loc.input.help.optionalItem3Version": "El número de versión del tercer elemento opcional que se va a incluir.", + "loc.input.label.optionalItem3ProcessorArchitecture": "Elemento opcional 3: arquitectura del procesador", + "loc.input.help.optionalItem3ProcessorArchitecture": "Arquitectura de procesador del tercer elemento opcional que se va a incluir.", + "loc.input.label.optionalItem3URI": "Elemento opcional 3: URI", + "loc.input.help.optionalItem3URI": "URI del tercer elemento opcional que se va a incluir.", + "loc.input.label.addDependency1": "Agregar una dependencia", + "loc.input.help.addDependency1": "Agregar una dependencia.", + "loc.input.label.dependency1Name": "Dependencia 1: nombre", + "loc.input.help.dependency1Name": "Nombre de la primera dependencia que se va a incluir.", + "loc.input.label.dependency1Publisher": "Dependencia 1: publicador", + "loc.input.help.dependency1Publisher": "Nombre del editor de la primera dependencia que se va a incluir.", + "loc.input.label.dependency1Version": "Dependencia 1: versión", + "loc.input.help.dependency1Version": "El número de versión de la primera dependencia que se va a incluir.", + "loc.input.label.dependency1ProcessorArchitecture": "Dependencia 1: arquitectura del procesador", + "loc.input.help.dependency1ProcessorArchitecture": "Arquitectura de procesador de la primera dependencia que se va a incluir.", + "loc.input.label.dependency1URI": "Dependencia 1: URI", + "loc.input.help.dependency1URI": "URI de la primera dependencia que se va a incluir.", + "loc.input.label.addDependency2": "Agregar una segunda dependencia", + "loc.input.help.addDependency2": "Agregar una segunda dependencia.", + "loc.input.label.dependency2Name": "Dependencia 2: nombre", + "loc.input.help.dependency2Name": "Nombre de la segunda dependencia que se va a incluir.", + "loc.input.label.dependency2Publisher": "Dependencia 2: publicador", + "loc.input.help.dependency2Publisher": "Nombre del editor de la segunda dependencia que se va a incluir.", + "loc.input.label.dependency2Version": "Dependencia 2: versión", + "loc.input.help.dependency2Version": "El número de versión de la segunda dependencia que se va a incluir.", + "loc.input.label.dependency2ProcessorArchitecture": "Dependencia 2: arquitectura del procesador", + "loc.input.help.dependency2ProcessorArchitecture": "Arquitectura de procesador de la segunda dependencia que se va a incluir.", + "loc.input.label.dependency2URI": "Dependencia 2: URI", + "loc.input.help.dependency2URI": "URI de la segunda dependencia que se va a incluir.", + "loc.input.label.addDependency3": "Agregue una tercera dependencia", + "loc.input.help.addDependency3": "Agregue una tercera dependencia.", + "loc.input.label.dependency3Name": "Dependencia 3: nombre", + "loc.input.help.dependency3Name": "Nombre de la tercera dependencia que se va a incluir.", + "loc.input.label.dependency3Publisher": "Dependencia 3: publicador", + "loc.input.help.dependency3Publisher": "Nombre del editor de la tercera dependencia que se va a incluir.", + "loc.input.label.dependency3Version": "Dependencia 3: versión", + "loc.input.help.dependency3Version": "Número de versión de la tercera dependencia que se va a incluir.", + "loc.input.label.dependency3ProcessorArchitecture": "Dependencia 3: arquitectura del procesador", + "loc.input.help.dependency3ProcessorArchitecture": "Arquitectura de procesador de la tercera dependencia que se va a incluir.", + "loc.input.label.dependency3URI": "Dependencia 3: URI", + "loc.input.help.dependency3URI": "URI de la tercera dependencia que se va a incluir." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/fr-FR/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/fr-FR/resources.resjson new file mode 100644 index 000000000..66585bb73 --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/fr-FR/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "Fichier d’installation de l’application pour MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Créer ou mettre à jour un fichier d’installation d’application pour les applications MSIX", + "loc.instanceNameFormat": "Créer un fichier d’installation de l’application", + "loc.input.label.package": "Package", + "loc.input.help.package": "Chemin d’accès du package ou bundle.", + "loc.input.label.outputPath": "Chemin d’accès du fichier de sortie", + "loc.input.help.outputPath": "Chemin d’accès du programme d’installation de l’application à écrire.", + "loc.input.label.method": "Méthode pour créer un fichier d’installation d’application", + "loc.input.help.method": "Méthode utilisée pour créer le fichier d’installation de l’application.", + "loc.input.label.existingFile": "Chemin d’accès du fichier d’installation de l’application existant", + "loc.input.help.existingFile": "Chemin d’accès du fichier d’installation de l’application existant à mettre à jour.", + "loc.input.label.versionUpdateMethod": "Méthode de mise à jour de la version du fichier d’installation de l’application", + "loc.input.help.versionUpdateMethod": "Incrémentez la révision principale/secondaire/build/révision du fichier d’installation de l’application, ou entrez un numéro de version à utiliser.", + "loc.input.label.fileVersion": "Version du fichier d’installation de l’application", + "loc.input.help.fileVersion": "Numéro de version qui sera donné. Doit prendre la forme (majeure).(mineur).(build).(révision).", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "URI web vers le fichier du programme d’installation de l’application redirigé.", + "loc.input.label.mainItemUri": "URI du principal package/bundle", + "loc.input.help.mainItemUri": "URI de l’emplacement du package de l’application ou du bundle.", + "loc.input.label.updateOnLaunch": "Mise à jour au démarrage", + "loc.input.help.updateOnLaunch": "Configurez l’application pour rechercher les mises à jour lorsqu’elle est lancée.", + "loc.input.label.hoursBetweenUpdateChecks": "Heures entre les vérifications de mise à jour", + "loc.input.help.hoursBetweenUpdateChecks": "La fréquence à laquelle le système vérifie l’existence de mises à jour pour l’application.", + "loc.input.label.showPromptWhenUpdating": "Afficher l’interface utilisateur à l’utilisateur lors de la mise à jour", + "loc.input.help.showPromptWhenUpdating": "Affichez une interface utilisateur pour avertir l’utilisateur si une mise à jour a lieu lors du lancement de l’application.", + "loc.input.label.updateBlocksActivation": "La mise à jour bloque l’activation de l’application", + "loc.input.help.updateBlocksActivation": "Bloquer le lancement de l’application jusqu’à la fin de la mise à jour.", + "loc.input.label.addOptionalItem1": "Ajouter un package/bundle", + "loc.input.help.addOptionalItem1": "Ajouter un package/bundle (offre facultative)", + "loc.input.label.optionalItem1Name": "Élément facultatif 1 : nom", + "loc.input.help.optionalItem1Name": "Nom du package ou du bundle de votre premier élément facultatif à inclure.", + "loc.input.label.optionalItem1Publisher": "Élément facultatif 1 : Publisher", + "loc.input.help.optionalItem1Publisher": "Nom du serveur de publication du premier élément facultatif à inclure.", + "loc.input.label.optionalItem1Version": "Élément facultatif 1 : version", + "loc.input.help.optionalItem1Version": "Numéro de version du premier élément facultatif à inclure.", + "loc.input.label.optionalItem1ProcessorArchitecture": "Élément facultatif 1 : architecture du processeur", + "loc.input.help.optionalItem1ProcessorArchitecture": "Architecture de processeur du premier élément facultatif à inclure.", + "loc.input.label.optionalItem1URI": "Élément facultatif 1 : URI", + "loc.input.help.optionalItem1URI": "URI du premier élément facultatif à inclure.", + "loc.input.label.addOptionalItem2": "Ajouter un deuxième package/bundle (offre facultative)", + "loc.input.help.addOptionalItem2": "Ajouter un deuxième package/bundle (offre facultative)", + "loc.input.label.optionalItem2Name": "Élément facultatif 2 : nom", + "loc.input.help.optionalItem2Name": "Le nom du package ou du bundle de votre deuxième élément facultatif à inclure.", + "loc.input.label.optionalItem2Publisher": "Élément facultatif 2 : Publisher", + "loc.input.help.optionalItem2Publisher": "Nom du serveur de publication du deuxième élément facultatif à inclure.", + "loc.input.label.optionalItem2Version": "Élément facultatif 2 : version", + "loc.input.help.optionalItem2Version": "Numéro de version du deuxième élément facultatif à inclure.", + "loc.input.label.optionalItem2ProcessorArchitecture": "Élément facultatif 2 : architecture du processeur", + "loc.input.help.optionalItem2ProcessorArchitecture": "Architecture de processeur du second élément facultatif à inclure.", + "loc.input.label.optionalItem2URI": "Élément facultatif 2 : URI", + "loc.input.help.optionalItem2URI": "URI du deuxième élément facultatif à inclure.", + "loc.input.label.addOptionalItem3": "Ajouter un troisième package/bundle (offre facultative)", + "loc.input.help.addOptionalItem3": "Ajouter un troisième package/bundle (offre facultative)", + "loc.input.label.optionalItem3Name": "Élément facultatif 3 : nom", + "loc.input.help.optionalItem3Name": "Le nom du package ou du bundle de votre troisième élément facultatif à inclure.", + "loc.input.label.optionalItem3Publisher": "Élément facultatif 3 : Publisher", + "loc.input.help.optionalItem3Publisher": "Nom de l’éditeur du troisième élément facultatif à inclure.", + "loc.input.label.optionalItem3Version": "Élément facultatif 3 : version", + "loc.input.help.optionalItem3Version": "Numéro de version du troisième élément facultatif à inclure.", + "loc.input.label.optionalItem3ProcessorArchitecture": "Option 3 facultative : architecture du processeur", + "loc.input.help.optionalItem3ProcessorArchitecture": "Architecture de processeur du troisième élément facultatif à inclure.", + "loc.input.label.optionalItem3URI": "Élément facultatif 3 : URI", + "loc.input.help.optionalItem3URI": "URI du troisième élément facultatif à inclure.", + "loc.input.label.addDependency1": "Ajouter une dépendance", + "loc.input.help.addDependency1": "Ajouter une dépendance.", + "loc.input.label.dependency1Name": "Dépendance 1 : nom", + "loc.input.help.dependency1Name": "Nom de la première dépendance à inclure.", + "loc.input.label.dependency1Publisher": "Dépendance 1 : Publisher", + "loc.input.help.dependency1Publisher": "Nom du serveur de publication de la première dépendance à inclure.", + "loc.input.label.dependency1Version": "Dépendance 1 : version", + "loc.input.help.dependency1Version": "Numéro de version de la première dépendance à inclure.", + "loc.input.label.dependency1ProcessorArchitecture": "Dépendance 1 : architecture du processeur", + "loc.input.help.dependency1ProcessorArchitecture": "Architecture de processeur de la première dépendance à inclure.", + "loc.input.label.dependency1URI": "Dépendance 1 : URI", + "loc.input.help.dependency1URI": "URI de la première dépendance à inclure.", + "loc.input.label.addDependency2": "Ajouter une seconde dépendance", + "loc.input.help.addDependency2": "Ajouter une seconde dépendance.", + "loc.input.label.dependency2Name": "Dépendance 2 : nom", + "loc.input.help.dependency2Name": "Nom de la deuxième dépendance à inclure.", + "loc.input.label.dependency2Publisher": "Dépendance 2 : Publisher", + "loc.input.help.dependency2Publisher": "Nom du serveur de publication de la deuxième dépendance à inclure.", + "loc.input.label.dependency2Version": "Dépendance 2 : version", + "loc.input.help.dependency2Version": "Numéro de version de la deuxième dépendance à inclure.", + "loc.input.label.dependency2ProcessorArchitecture": "Dépendance 2 : architecture du processeur", + "loc.input.help.dependency2ProcessorArchitecture": "L’architecture du processeur de la deuxième dépendance à inclure.", + "loc.input.label.dependency2URI": "Dépendance 2 : URI", + "loc.input.help.dependency2URI": "URI de la deuxième dépendance à inclure.", + "loc.input.label.addDependency3": "Ajoutez une troisième dépendance", + "loc.input.help.addDependency3": "Ajoutez une troisième dépendance.", + "loc.input.label.dependency3Name": "Dépendance 3 : nom", + "loc.input.help.dependency3Name": "Nom de la troisième dépendance à inclure.", + "loc.input.label.dependency3Publisher": "Dépendance 3 : Publisher", + "loc.input.help.dependency3Publisher": "Nom du serveur de publication de la troisième dépendance à inclure.", + "loc.input.label.dependency3Version": "Dépendance 3 : version", + "loc.input.help.dependency3Version": "Numéro de version de la troisième dépendance à inclure.", + "loc.input.label.dependency3ProcessorArchitecture": "Dépendance 3 : architecture du processeur", + "loc.input.help.dependency3ProcessorArchitecture": "Architecture de processeur de la troisième dépendance à inclure.", + "loc.input.label.dependency3URI": "Dépendance 3 : URI", + "loc.input.help.dependency3URI": "URI de la troisième dépendance à inclure." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/it-IT/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/it-IT/resources.resjson new file mode 100644 index 000000000..a9fb8d220 --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/it-IT/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "File del programma di installazione app per MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Creare o aggiornare un file del programma di installazione app per le app di MSIX", + "loc.instanceNameFormat": "Crea file del programma di installazione app", + "loc.input.label.package": "Pacchetto", + "loc.input.help.package": "Percorso del pacchetto o del bundle.", + "loc.input.label.outputPath": "Percorso del file di output", + "loc.input.help.outputPath": "Percorso del programma di installazione di app da scrivere.", + "loc.input.label.method": "Metodo per creare file del programma di installazione app", + "loc.input.help.method": "Metodo utilizzato per creare il file del programma di installazione dell'app.", + "loc.input.label.existingFile": "Percorso del file del programma di installazione dell'app esistente", + "loc.input.help.existingFile": "Percorso del file del programma di installazione app esistente da aggiornare.", + "loc.input.label.versionUpdateMethod": "Metodo per aggiornare la versione del file del programma di installazione app", + "loc.input.help.versionUpdateMethod": "Incrementare i valori maggiore/minore/build/revisione del file del programma di installazione app, o immettere un numero versione da usare.", + "loc.input.label.fileVersion": "Versionene del file del programma di installazione app", + "loc.input.help.fileVersion": "Il numero versione che sarà assegnato. Deve avere il formato: (maggiore).(minore).(build).(revisione).", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "URI Web del file del programma di installazione app reindirizzato.", + "loc.input.label.mainItemUri": "Pacchetto/bundle URI principale", + "loc.input.help.mainItemUri": "URI al percorso app pacchetto/bundle.", + "loc.input.label.updateOnLaunch": "Aggiorna all'avvio", + "loc.input.help.updateOnLaunch": "Imposta l'app per verificare la disponibilità di aggiornamenti all'avvio.", + "loc.input.label.hoursBetweenUpdateChecks": "Ore tra un controllo degli aggiornamenti e l'altro", + "loc.input.help.hoursBetweenUpdateChecks": "Frequenza con cui il sistema controllerà la disponibilità di aggiornamenti all'app.", + "loc.input.label.showPromptWhenUpdating": "Mostrare interfaccia all'utente durante l'aggiornamento", + "loc.input.help.showPromptWhenUpdating": "Visualizza un'interfaccia utente per notificare all'utente se viene eseguito un aggiornamento durante l'avvio dell'app.", + "loc.input.label.updateBlocksActivation": "L'aggiornamento blocca l'attivazione dell'app", + "loc.input.help.updateBlocksActivation": "Blocca l'avvio dell'app fino al termine dell'aggiornamento.", + "loc.input.label.addOptionalItem1": "Aggiungere un pacchetto/bundle facoltativo", + "loc.input.help.addOptionalItem1": "Aggiungere un pacchetto o bundle facoltativo", + "loc.input.label.optionalItem1Name": "Elemento facoltativo 1: nome", + "loc.input.help.optionalItem1Name": "Il nome del pacchetto o del bundle del primo elemento facoltativo da includere.", + "loc.input.label.optionalItem1Publisher": "Elemento facoltativo 1: editore", + "loc.input.help.optionalItem1Publisher": "Nome dell'autore del primo elemento facoltativo da includere.", + "loc.input.label.optionalItem1Version": "Elemento facoltativo 1: versione", + "loc.input.help.optionalItem1Version": "Numero di versione del primo elemento facoltativo da includere.", + "loc.input.label.optionalItem1ProcessorArchitecture": "Elemento facoltativo 1: architettura del processore", + "loc.input.help.optionalItem1ProcessorArchitecture": "Architettura del processore del primo elemento facoltativo da includere.", + "loc.input.label.optionalItem1URI": "Elemento facoltativo 1: URI", + "loc.input.help.optionalItem1URI": "URI del primo elemento facoltativo da includere.", + "loc.input.label.addOptionalItem2": "Aggiungere un secondo pacchetto/bundle facoltativo", + "loc.input.help.addOptionalItem2": "Aggiungere un secondo pacchetto o bundle facoltativo", + "loc.input.label.optionalItem2Name": "Elemento facoltativo 2: nome", + "loc.input.help.optionalItem2Name": "Il nome del pacchetto o del bundle del secondo elemento facoltativo da includere.", + "loc.input.label.optionalItem2Publisher": "Elemento facoltativo 2: editore", + "loc.input.help.optionalItem2Publisher": "Nome dell'autore del secondo elemento facoltativo da includere.", + "loc.input.label.optionalItem2Version": "Elemento facoltativo 2: versione", + "loc.input.help.optionalItem2Version": "Numero di versione del secondo elemento facoltativo da includere.", + "loc.input.label.optionalItem2ProcessorArchitecture": "Elemento facoltativo 2: architettura del processore", + "loc.input.help.optionalItem2ProcessorArchitecture": "L'architettura del processore del secondo elemento facoltativo da includere.", + "loc.input.label.optionalItem2URI": "Elemento facoltativo 2: URI", + "loc.input.help.optionalItem2URI": "L'URI dell'secondo elemento facoltativo da includere.", + "loc.input.label.addOptionalItem3": "Aggiungere un terzo pacchetto/bundle facoltativo", + "loc.input.help.addOptionalItem3": "Aggiungere un terzo pacchetto o bundle facoltativo", + "loc.input.label.optionalItem3Name": "Elemento facoltativo 3: nome", + "loc.input.help.optionalItem3Name": "Il nome del pacchetto o del bundle del terzo elemento facoltativo da includere.", + "loc.input.label.optionalItem3Publisher": "Elemento facoltativo 3: editore", + "loc.input.help.optionalItem3Publisher": "Nome dell'autore del terzo elemento facoltativo da includere.", + "loc.input.label.optionalItem3Version": "Elemento facoltativo 3: versione", + "loc.input.help.optionalItem3Version": "Numero di versione del terzo elemento facoltativo da includere.", + "loc.input.label.optionalItem3ProcessorArchitecture": "Elemento facoltativo 3: architettura del processore", + "loc.input.help.optionalItem3ProcessorArchitecture": "L'architettura del processore della terza dipendenza da aggiungere.", + "loc.input.label.optionalItem3URI": "Elemento facoltativo 3: URI", + "loc.input.help.optionalItem3URI": "URI del terzo elemento facoltativo da includere.", + "loc.input.label.addDependency1": "Aggiungi dipendenza", + "loc.input.help.addDependency1": "Aggiungere una dipendenza.", + "loc.input.label.dependency1Name": "Dipendenza 1: nome", + "loc.input.help.dependency1Name": "Nome della prima relazione da includere.", + "loc.input.label.dependency1Publisher": "Dipendenza 1: editore", + "loc.input.help.dependency1Publisher": "Nome dell'entità di pubblicazione della prima dipendenza da includere.", + "loc.input.label.dependency1Version": "Dipendenza 1: versione", + "loc.input.help.dependency1Version": "Numero di versione della prima relazione da includere.", + "loc.input.label.dependency1ProcessorArchitecture": "Elemento facoltativo 1: architettura del processore", + "loc.input.help.dependency1ProcessorArchitecture": "L'architettura del processore della prima dipendenza da includere.", + "loc.input.label.dependency1URI": "Dipendenza 1: URI", + "loc.input.help.dependency1URI": "L'URI della prima relazione da includere.", + "loc.input.label.addDependency2": "Aggiungere una seconda dipendenza", + "loc.input.help.addDependency2": "Aggiungere una seconda dipendenza.", + "loc.input.label.dependency2Name": "Dipendenza 2: nome", + "loc.input.help.dependency2Name": "Nome della seconda dipendenza da includere.", + "loc.input.label.dependency2Publisher": "Dipendenza 2: editore", + "loc.input.help.dependency2Publisher": "Nome dell'autore della seconda dipendenza da includere.", + "loc.input.label.dependency2Version": "Dipendenza 2: versione", + "loc.input.help.dependency2Version": "Numero di versione della seconda dipendenza da includere.", + "loc.input.label.dependency2ProcessorArchitecture": "Elemento facoltativo 2: architettura del processore", + "loc.input.help.dependency2ProcessorArchitecture": "L'architettura del processore della seconda dipendenza da includere.", + "loc.input.label.dependency2URI": "Dipendenza 2: URI", + "loc.input.help.dependency2URI": "L'URI della seconda dipendenza da includere.", + "loc.input.label.addDependency3": "Aggiungere una terza dipendenza", + "loc.input.help.addDependency3": "Aggiungere una terza dipendenza.", + "loc.input.label.dependency3Name": "Dipendenza 3: nome", + "loc.input.help.dependency3Name": "Nome della terza dipendenza da includere.", + "loc.input.label.dependency3Publisher": "Dipendenza 3: editore", + "loc.input.help.dependency3Publisher": "Nome dell'autore della terza dipendenza da includere.", + "loc.input.label.dependency3Version": "Dipendenza 3: versione", + "loc.input.help.dependency3Version": "Numero di versione della terza dipendenza da includere.", + "loc.input.label.dependency3ProcessorArchitecture": "Elemento facoltativo 3: architettura del processore", + "loc.input.help.dependency3ProcessorArchitecture": "L'architettura del processore della terza dipendenza da includere.", + "loc.input.label.dependency3URI": "Dipendenza 3: URI", + "loc.input.help.dependency3URI": "URI della terza dipendenza da includere." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/ja-JP/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/ja-JP/resources.resjson new file mode 100644 index 000000000..ab06c8d67 --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/ja-JP/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "MSIX 用アプリ インストーラー ファイル", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "MSIX アプリのアプリ インストーラー ファイルを作成または更新する", + "loc.instanceNameFormat": "アプリ インストーラー ファイルの作成", + "loc.input.label.package": "パッケージ", + "loc.input.help.package": "パッケージまたはバンドルのパスです。", + "loc.input.label.outputPath": "出力ファイルのパス", + "loc.input.help.outputPath": "記述するアプリのインストーラーのパスです。", + "loc.input.label.method": "アプリ インストーラー ファイルの作成方法", + "loc.input.help.method": "アプリのインストーラーファイルを作成するために使用されるメソッドです。", + "loc.input.label.existingFile": "既存のアプリのインストーラー ファイルへのパス", + "loc.input.help.existingFile": "更新する既存のアプリインストーラーファイルのパスです。", + "loc.input.label.versionUpdateMethod": "アプリ インストーラー ファイルのバージョンを更新する方法", + "loc.input.help.versionUpdateMethod": "アプリ インストーラー ファイルのメジャー / マイナー / ビルド / リビジョンの番号を 1 つ増やすか、使用するバージョン番号を入力します。", + "loc.input.label.fileVersion": "アプリ インストーラー ファイルのバージョン", + "loc.input.help.fileVersion": "指定するバージョン番号です。(メジャー).(マイナー).(ビルド).(リビジョン) の形にする必要があります。", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "リダイレクトされたアプリ インストーラー ファイルの Web URI です。", + "loc.input.label.mainItemUri": "メインパッケージ / バンドル の URI", + "loc.input.help.mainItemUri": "アプリ パッケージ / バンドルの場所の URI です。", + "loc.input.label.updateOnLaunch": "起動時に更新", + "loc.input.help.updateOnLaunch": "アプリが起動時に更新を確認するように設定します。", + "loc.input.label.hoursBetweenUpdateChecks": "更新プログラムのチェック間隔 (時間)", + "loc.input.help.hoursBetweenUpdateChecks": "システムがアプリの更新を確認する頻度です。", + "loc.input.label.showPromptWhenUpdating": "更新時に UI をユーザーに表示する", + "loc.input.help.showPromptWhenUpdating": "アプリを起動したときに更新が発生した場合にユーザーに通知するための UI を表示します。", + "loc.input.label.updateBlocksActivation": "更新プログラムでアプリの起動をブロックする", + "loc.input.help.updateBlocksActivation": "更新が完了するまで、アプリの起動をブロックします。", + "loc.input.label.addOptionalItem1": "オプションのパッケージ / バンドルを追加する", + "loc.input.help.addOptionalItem1": "オプションのパッケージまたはバンドルを追加する", + "loc.input.label.optionalItem1Name": "オプションのアイテム 1: 名前", + "loc.input.help.optionalItem1Name": "含める必要がある 1 つ目のオプションのアイテムのパッケージ名またはバンドル名です。", + "loc.input.label.optionalItem1Publisher": "オプションのアイテム 1: 発行元", + "loc.input.help.optionalItem1Publisher": "追加する 1 番目のオプションのアイテムの発行元名です。", + "loc.input.label.optionalItem1Version": "オプションのアイテム 1: バージョン", + "loc.input.help.optionalItem1Version": "追加する 1 番目のオプションのアイテムのバージョン番号です。", + "loc.input.label.optionalItem1ProcessorArchitecture": "オプションのアイテム 1: プロセッサ アーキテクチャ", + "loc.input.help.optionalItem1ProcessorArchitecture": "追加する 1 番目のオプションのアイテムのプロセッサ アーキテクチャです。", + "loc.input.label.optionalItem1URI": "オプションのアイテム 1: URI", + "loc.input.help.optionalItem1URI": "追加する 1 番目のオプションのアイテムの URI です。", + "loc.input.label.addOptionalItem2": "2 つ目のオプションのパッケージ / バンドルを追加する", + "loc.input.help.addOptionalItem2": "2 つ目のオプションのパッケージまたはバンドルを追加する", + "loc.input.label.optionalItem2Name": "オプションのアイテム 2: 名前", + "loc.input.help.optionalItem2Name": "含める必要がある 2 つ目のオプションのアイテムのパッケージ名またはバンドル名です。", + "loc.input.label.optionalItem2Publisher": "オプションのアイテム 2: 発行元", + "loc.input.help.optionalItem2Publisher": "追加する2番目のオプション項目の発行者名です。", + "loc.input.label.optionalItem2Version": "オプションのアイテム 2: バージョン", + "loc.input.help.optionalItem2Version": "追加する 2 番目のオプションのアイテムのバージョン番号です。", + "loc.input.label.optionalItem2ProcessorArchitecture": "オプションのアイテム 2: プロセッサ アーキテクチャ", + "loc.input.help.optionalItem2ProcessorArchitecture": "追加する 2 番目のオプションのアイテムのプロセッサ アーキテクチャです。", + "loc.input.label.optionalItem2URI": "オプションのアイテム 2: URI", + "loc.input.help.optionalItem2URI": "追加する 2 番目のオプションのアイテムの URI です。", + "loc.input.label.addOptionalItem3": "3 つ目のオプションのパッケージ / バンドルを追加する", + "loc.input.help.addOptionalItem3": "3 つ目のオプションのパッケージまたはバンドルを追加する", + "loc.input.label.optionalItem3Name": "オプションのアイテム 3: 名前", + "loc.input.help.optionalItem3Name": "含める必要がある 3 つ目のオプションのアイテムのパッケージ名またはバンドル名です。", + "loc.input.label.optionalItem3Publisher": "オプションのアイテム 3: 発行元", + "loc.input.help.optionalItem3Publisher": "追加する 3 番目のオプションのアイテムの発行元名です。", + "loc.input.label.optionalItem3Version": "オプションのアイテム 3: バージョン", + "loc.input.help.optionalItem3Version": "追加する 3 番目のオプションのアイテムのバージョン番号です。", + "loc.input.label.optionalItem3ProcessorArchitecture": "オプションのアイテム 3: プロセッサ アーキテクチャ", + "loc.input.help.optionalItem3ProcessorArchitecture": "追加する 3 番目のオプションのアイテムのプロセッサ アーキテクチャです。", + "loc.input.label.optionalItem3URI": "オプションのアイテム 3: URI", + "loc.input.help.optionalItem3URI": "追加する 3 番目のオプションのアイテムの URI です。", + "loc.input.label.addDependency1": "依存関係を追加する", + "loc.input.help.addDependency1": "依存関係を追加します。", + "loc.input.label.dependency1Name": "依存関係 1: 名前", + "loc.input.help.dependency1Name": "含める最初の依存関係の名前です。", + "loc.input.label.dependency1Publisher": "依存関係 1: 発行元", + "loc.input.help.dependency1Publisher": "含める最初の依存関係の発行元の名前です。", + "loc.input.label.dependency1Version": "依存関係 1: バージョン", + "loc.input.help.dependency1Version": "追加する 1 番目の依存関係のバージョン番号です。", + "loc.input.label.dependency1ProcessorArchitecture": "依存関係 1: プロセッサ アーキテクチャ", + "loc.input.help.dependency1ProcessorArchitecture": "追加する 1 番目の依存関係のプロセッサ アーキテクチャです。", + "loc.input.label.dependency1URI": "依存関係 1: URI", + "loc.input.help.dependency1URI": "含める最初の依存関係の URI。", + "loc.input.label.addDependency2": "2 番目の依存関係を追加する", + "loc.input.help.addDependency2": "2 番目の依存関係を追加します。", + "loc.input.label.dependency2Name": "依存関係 2: 名前", + "loc.input.help.dependency2Name": "追加する 2 番目の依存関係の名前です。", + "loc.input.label.dependency2Publisher": "依存関係 2: 発行元", + "loc.input.help.dependency2Publisher": "含める2番目の依存関係の発行者名です。", + "loc.input.label.dependency2Version": "依存関係 2: バージョン", + "loc.input.help.dependency2Version": "追加する 2 番目の依存関係のバージョン番号です。", + "loc.input.label.dependency2ProcessorArchitecture": "依存関係 2: プロセッサ アーキテクチャ", + "loc.input.help.dependency2ProcessorArchitecture": "追加する 2 番目の依存関係のプロセッサ アーキテクチャです。", + "loc.input.label.dependency2URI": "依存関係 2: URI", + "loc.input.help.dependency2URI": "追加する 2 番目の依存関係の URI です。", + "loc.input.label.addDependency3": "3 番目の依存関係を追加する", + "loc.input.help.addDependency3": "3 番目の依存関係を追加します。", + "loc.input.label.dependency3Name": "依存関係 3: 名前", + "loc.input.help.dependency3Name": "追加する 3 番目の依存関係の名前です。", + "loc.input.label.dependency3Publisher": "依存関係 3: 発行元", + "loc.input.help.dependency3Publisher": "追加する 3 番目の依存関係の発行元名です。", + "loc.input.label.dependency3Version": "依存関係 3: バージョン", + "loc.input.help.dependency3Version": "追加する 3 番目の依存関係のバージョン番号です。", + "loc.input.label.dependency3ProcessorArchitecture": "依存関係 3: プロセッサ アーキテクチャ", + "loc.input.help.dependency3ProcessorArchitecture": "追加する 3 番目の依存関係のプロセッサ アーキテクチャです。", + "loc.input.label.dependency3URI": "依存関係 3: URI", + "loc.input.help.dependency3URI": "追加する 3 番目の依存関係の URI です。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/ko-KR/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/ko-KR/resources.resjson new file mode 100644 index 000000000..09eb79812 --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/ko-KR/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "MSIX용 앱 설치 관리자 파일", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "MSIX 앱용 앱 설치 관리자 파일 만들기 또는 업데이트", + "loc.instanceNameFormat": "앱 설치 관리자 파일 만들기", + "loc.input.label.package": "패키지", + "loc.input.help.package": "패키지 또는 번들의 경로입니다.", + "loc.input.label.outputPath": "출력 파일 경로", + "loc.input.help.outputPath": "쓰려는 앱 설치 관리자의 경로입니다.", + "loc.input.label.method": "앱 설치 관리자 파일을 만드는 방법", + "loc.input.help.method": "앱 설치 관리자 파일을 만드는 데 사용하는 메서드입니다.", + "loc.input.label.existingFile": "기존 앱 설치 관리자 파일의 경로", + "loc.input.help.existingFile": "업데이트할 기존 앱 설치 관리자 파일의 경로입니다.", + "loc.input.label.versionUpdateMethod": "앱 설치 관리자 파일 버전을 업데이트하는 방법", + "loc.input.help.versionUpdateMethod": "앱 설치 관리자 파일의 주/부/보조/수정 버전을 늘리거나 사용할 버전번호를 입력하세요.", + "loc.input.label.fileVersion": "앱 설치 관리자 파일 버전", + "loc.input.help.fileVersion": "제공되는 버전 번호입니다. 양식(주)을 가져야 합니다. (부). (빌드). (수정 버전).", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "리디렉션된 앱 설치 관리자 파일의 웹 URI입니다.", + "loc.input.label.mainItemUri": "기본 패키지/번들 URI", + "loc.input.help.mainItemUri": "앱 패키지/번들 위치에 대한 URI입니다.", + "loc.input.label.updateOnLaunch": "시작할 때 업데이트", + "loc.input.help.updateOnLaunch": "시작할 때 업데이트를 확인하도록 앱을 설정하십시오.", + "loc.input.label.hoursBetweenUpdateChecks": "업데이트 확인 시간 간격", + "loc.input.help.hoursBetweenUpdateChecks": "시스템에서 앱 업데이트를 얼마나 자주 확인합니다.", + "loc.input.label.showPromptWhenUpdating": "업데이트할 때 사용자에게 UI 표시", + "loc.input.help.showPromptWhenUpdating": "앱을 시작할 때 업데이트가 발생할 경우 사용자에게 알리는 UI를 표시합니다.", + "loc.input.label.updateBlocksActivation": "블록 앱 활성화 업데이트", + "loc.input.help.updateBlocksActivation": "업데이트가 완료될 때까지 앱 시작을 차단합니다.", + "loc.input.label.addOptionalItem1": "옵션 패키지/번들 추가", + "loc.input.help.addOptionalItem1": "선택적 패키지 또는 번들 추가", + "loc.input.label.optionalItem1Name": "선택 항목 1: 이름", + "loc.input.help.optionalItem1Name": "포함할 첫 번째 선택 항목의 패키지 또는 번들 이름입니다.", + "loc.input.label.optionalItem1Publisher": "선택 항목 1: 게시자", + "loc.input.help.optionalItem1Publisher": "포함할 첫 번째 선택적 항목의 게시자 이름입니다.", + "loc.input.label.optionalItem1Version": "선택 항목 1: 버전", + "loc.input.help.optionalItem1Version": "포함할 첫 번째 선택적 항목의 버전 번호입니다.", + "loc.input.label.optionalItem1ProcessorArchitecture": "선택 항목 1: 프로세서 아키텍처", + "loc.input.help.optionalItem1ProcessorArchitecture": "포함할 첫 번째 선택적 항목의 프로세서 아키텍처입니다.", + "loc.input.label.optionalItem1URI": "선택 항목 1: URI", + "loc.input.help.optionalItem1URI": "포함할 첫 번째 선택적 항목의 URI입니다.", + "loc.input.label.addOptionalItem2": "두 번째 옵션 패키지/번들 추가", + "loc.input.help.addOptionalItem2": "두 번째 선택적 패키지 또는 번들 추가", + "loc.input.label.optionalItem2Name": "선택 항목 2: 이름", + "loc.input.help.optionalItem2Name": "포함할 두 번째 선택 항목의 패키지 또는 번들 이름입니다.", + "loc.input.label.optionalItem2Publisher": "선택 항목 2: 게시자", + "loc.input.help.optionalItem2Publisher": "포함할 두 번째 선택적 항목의 게시자 이름입니다.", + "loc.input.label.optionalItem2Version": "선택 항목 2: 버전", + "loc.input.help.optionalItem2Version": "포함할 두 번째 선택 항목의 버전 번호입니다.", + "loc.input.label.optionalItem2ProcessorArchitecture": "선택 항목 2: 프로세서 아키텍처", + "loc.input.help.optionalItem2ProcessorArchitecture": "포함할 보조 선택 항목의 프로세서 아이텍처입니다.", + "loc.input.label.optionalItem2URI": "선택 항목 2: URI", + "loc.input.help.optionalItem2URI": "포함할 두 번째 선택 항목의 URI입니다.", + "loc.input.label.addOptionalItem3": "세 번째 옵션 패키지/번들 추가", + "loc.input.help.addOptionalItem3": "세 번째 선택적 패키지 또는 번들 추가", + "loc.input.label.optionalItem3Name": "선택 항목 3: 이름", + "loc.input.help.optionalItem3Name": "포함할 세 번째 선택 항목의 패키지 또는 번들 이름입니다.", + "loc.input.label.optionalItem3Publisher": "선택 항목 3: 게시자", + "loc.input.help.optionalItem3Publisher": "포함할 세 번째 선택항목의 게시자 이름입니다.", + "loc.input.label.optionalItem3Version": "선택 항목 3: 버전", + "loc.input.help.optionalItem3Version": "포함할 세 번째 선택 항목의 버전 번호입니다.", + "loc.input.label.optionalItem3ProcessorArchitecture": "선택 항목 3: 프로세서 아키텍처", + "loc.input.help.optionalItem3ProcessorArchitecture": "포함할 세 번째 선택적 항목의 프로세서 아키텍처입니다.", + "loc.input.label.optionalItem3URI": "선택 항목 3: URI", + "loc.input.help.optionalItem3URI": "포함할 세 번째 선택 항목의 URI입니다.", + "loc.input.label.addDependency1": "종속성 추가", + "loc.input.help.addDependency1": "종속성을 추가합니다.", + "loc.input.label.dependency1Name": "종속성 1: 이름", + "loc.input.help.dependency1Name": "포함할 첫 번째 의존 관계의 이름입니다.", + "loc.input.label.dependency1Publisher": "종속성 1: 게시자", + "loc.input.help.dependency1Publisher": "첫 번째 종속성의 게시자 이름입니다.", + "loc.input.label.dependency1Version": "종속성 1: 버전", + "loc.input.help.dependency1Version": "포함할 첫 번째 의존 관계의 버전 번호입니다.", + "loc.input.label.dependency1ProcessorArchitecture": "종속성 1: 프로세서 아키텍처", + "loc.input.help.dependency1ProcessorArchitecture": "포함할 첫 번째 종속성의 프로세서 아키텍처입니다.", + "loc.input.label.dependency1URI": "종속성 1: URI", + "loc.input.help.dependency1URI": "첫 번째 종속성의 URI입니다.", + "loc.input.label.addDependency2": "두 번째 종속성 추가", + "loc.input.help.addDependency2": "두 번째 종속성을 추가합니다.", + "loc.input.label.dependency2Name": "종속성 2: 이름", + "loc.input.help.dependency2Name": "포함할 두 번째 종속성의 이름입니다.", + "loc.input.label.dependency2Publisher": "종속성 2: 게시자", + "loc.input.help.dependency2Publisher": "포함할 두 번째 종속성의 게시자 이름입니다.", + "loc.input.label.dependency2Version": "종속성 2: 버전", + "loc.input.help.dependency2Version": "포함할 두 번째 종속성의 버전 번호입니다.", + "loc.input.label.dependency2ProcessorArchitecture": "종속성 2: 프로세서 아키텍처", + "loc.input.help.dependency2ProcessorArchitecture": "포함할 두 번째 종속성의 프로세서 아키텍처입니다.", + "loc.input.label.dependency2URI": "종속성 2: URI", + "loc.input.help.dependency2URI": "포함할 두 번째 종속성의 URI입니다.", + "loc.input.label.addDependency3": "세 번째 종속성 추가", + "loc.input.help.addDependency3": "세 번째 종속성을 추가합니다.", + "loc.input.label.dependency3Name": "종속성 3: 이름", + "loc.input.help.dependency3Name": "포함할 세 번째 종속성의 이름입니다.", + "loc.input.label.dependency3Publisher": "종속성 3: 게시자", + "loc.input.help.dependency3Publisher": "포함할 세 번째 종속성의 게시자 이름입니다.", + "loc.input.label.dependency3Version": "종속성 3: 버전", + "loc.input.help.dependency3Version": "포함할 세 번째 종속성의 버전 번호입니다.", + "loc.input.label.dependency3ProcessorArchitecture": "종속성 3: 프로세서 아키텍처", + "loc.input.help.dependency3ProcessorArchitecture": "포함할 세 번째 종속성의 프로세서 아키텍처입니다.", + "loc.input.label.dependency3URI": "종속성 3: URI", + "loc.input.help.dependency3URI": "세 번째 종속성의 URI입니다." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/pt-BR/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/pt-BR/resources.resjson new file mode 100644 index 000000000..e05afe4d3 --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/pt-BR/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "Arquivo do Instalador de Aplicativo para MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Criar ou atualizar um arquivo do Instalador de Aplicativo para os aplicativos do MSIX", + "loc.instanceNameFormat": "Método para Criar o arquivo do Instalador de Aplicativo", + "loc.input.label.package": "Pacote", + "loc.input.help.package": "Caminho para o pacote ou lote.", + "loc.input.label.outputPath": "Nome do Arquivo de Saída", + "loc.input.help.outputPath": "O caminho do instalador do aplicativo a ser gravado.", + "loc.input.label.method": "Método para Criar o Arquivo do Instalador de Aplicativo", + "loc.input.help.method": "Método usado para criar o arquivo do instalador do aplicativo.", + "loc.input.label.existingFile": "Caminho ao Arquivo do Instalador de Aplicativo Existente", + "loc.input.help.existingFile": "O caminho do arquivo de instalador de aplicativos existente a ser atualizado.", + "loc.input.label.versionUpdateMethod": "Método para atualizar a versão do arquivo de instalador do aplicativo", + "loc.input.help.versionUpdateMethod": "Incrementa a principal/secundária/compilação/revisão do arquivo do instalador do aplicativo ou digite um número de versão a ser usado.", + "loc.input.label.fileVersion": "Versão do arquivo Instalador de Aplicativos", + "loc.input.help.fileVersion": "O número da versão que será fornecido. Deve ter a forma (principal).(secundária).(compilação).(revisão).", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "URL da Web para o arquivo de instalador do aplicativo Redirecionado.", + "loc.input.label.mainItemUri": "URI principal do lote/pacote", + "loc.input.help.mainItemUri": "URI para o local do pacote/lote do aplicativo.", + "loc.input.label.updateOnLaunch": "Atualizar ao Iniciar", + "loc.input.help.updateOnLaunch": "Defina o aplicativo para verificar se há atualizações quando for iniciado.", + "loc.input.label.hoursBetweenUpdateChecks": "Horas Entre as Verificações de Atualização", + "loc.input.help.hoursBetweenUpdateChecks": "Frequência com que o sistema verificará se há atualizações para o aplicativo.", + "loc.input.label.showPromptWhenUpdating": "Mostrar a interface do usuário para Usuário ao Atualizar", + "loc.input.help.showPromptWhenUpdating": "Mostre uma interface de usuário para notificar o usuário se ocorrer uma atualização ao iniciar o aplicativo.", + "loc.input.label.updateBlocksActivation": "Atualizar Ativação do Aplicativo Blocks", + "loc.input.help.updateBlocksActivation": "Bloquear a inicialização do aplicativo até que a atualização seja concluída.", + "loc.input.label.addOptionalItem1": "Adicionar um pacote/lote opcional", + "loc.input.help.addOptionalItem1": "Adicionar um pacote ou lote opcional", + "loc.input.label.optionalItem1Name": "Item Opcional 1: Nome", + "loc.input.help.optionalItem1Name": "O nome do pacote ou do lote do seu primeiro item opcional a ser incluído.", + "loc.input.label.optionalItem1Publisher": "Item Opcional 1: Fornecedor", + "loc.input.help.optionalItem1Publisher": "O nome do editor do primeiro item opcional a ser incluído.", + "loc.input.label.optionalItem1Version": "Item opcional 1: Versão", + "loc.input.help.optionalItem1Version": "O número da versão do primeiro item opcional a ser incluído.", + "loc.input.label.optionalItem1ProcessorArchitecture": "Item opcional 1: Arquitetura do Processador", + "loc.input.help.optionalItem1ProcessorArchitecture": "A arquitetura de processador do primeiro item opcional a ser incluído.", + "loc.input.label.optionalItem1URI": "Item Opcional 1: URI", + "loc.input.help.optionalItem1URI": "O URI do primeiro item opcional a ser incluído.", + "loc.input.label.addOptionalItem2": "Adicionar um segundo pacote/lote opcional", + "loc.input.help.addOptionalItem2": "Adicionar um segundo lote ou pacote opcional", + "loc.input.label.optionalItem2Name": "Item opcional 2: Nome", + "loc.input.help.optionalItem2Name": "O nome do pacote ou do lote do segundo item opcional a ser incluído.", + "loc.input.label.optionalItem2Publisher": "Item opcional 2: Fornecedor", + "loc.input.help.optionalItem2Publisher": "O nome do editor do segundo item opcional a ser incluído.", + "loc.input.label.optionalItem2Version": "Item Opcional 2: Versão", + "loc.input.help.optionalItem2Version": "O número de versão do segundo item opcional a ser incluído.", + "loc.input.label.optionalItem2ProcessorArchitecture": "Item Opcional 2: Arquitetura do Processador", + "loc.input.help.optionalItem2ProcessorArchitecture": "A arquitetura de processador do segundo item opcional a ser incluído.", + "loc.input.label.optionalItem2URI": "Item opcional 2: URI", + "loc.input.help.optionalItem2URI": "O URI do segundo item opcional a ser incluído.", + "loc.input.label.addOptionalItem3": "Adicionar um terceiro pacote/lote opcional", + "loc.input.help.addOptionalItem3": "Adicionar um terceiro lote ou pacote opcional", + "loc.input.label.optionalItem3Name": "Item opcional 3: Nome", + "loc.input.help.optionalItem3Name": "O nome do pacote ou do lote do terceiro item opcional a ser incluído.", + "loc.input.label.optionalItem3Publisher": "Item opcional 3: Fornecedor", + "loc.input.help.optionalItem3Publisher": "O nome do editor do terceiro item opcional a ser incluído.", + "loc.input.label.optionalItem3Version": "Item opcional 3: Versão", + "loc.input.help.optionalItem3Version": "O número da versão do terceiro item opcional a ser incluído.", + "loc.input.label.optionalItem3ProcessorArchitecture": "Item 3 Opcional: Arquitetura do Processador", + "loc.input.help.optionalItem3ProcessorArchitecture": "A arquitetura de processador do terceiro item opcional a ser incluído.", + "loc.input.label.optionalItem3URI": "Item opcional 3: URI", + "loc.input.help.optionalItem3URI": "O URI do terceiro item opcional a ser incluído.", + "loc.input.label.addDependency1": "Adicionar uma Dependência", + "loc.input.help.addDependency1": "Adicione uma dependência.", + "loc.input.label.dependency1Name": "A dependência 1: Nome", + "loc.input.help.dependency1Name": "O nome da primeira dependência a ser incluída.", + "loc.input.label.dependency1Publisher": "A dependência 1: Fornecedor", + "loc.input.help.dependency1Publisher": "O nome do editor da primeira dependência a ser incluída.", + "loc.input.label.dependency1Version": "A dependência 1: Versão", + "loc.input.help.dependency1Version": "O número de versão da primeira dependência a ser incluída.", + "loc.input.label.dependency1ProcessorArchitecture": "A dependência 1: Arquitetura do Processador", + "loc.input.help.dependency1ProcessorArchitecture": "A arquitetura de processador da primeira dependência a ser incluída.", + "loc.input.label.dependency1URI": "A dependência 1: URI", + "loc.input.help.dependency1URI": "O URI da primeira dependência a ser incluída.", + "loc.input.label.addDependency2": "Adicionar uma Segunda Dependência", + "loc.input.help.addDependency2": "Adicione-o uma segunda dependência.", + "loc.input.label.dependency2Name": "A dependência 2: Nome", + "loc.input.help.dependency2Name": "O nome da segunda dependência a ser incluída.", + "loc.input.label.dependency2Publisher": "A dependência 2: Fornecedor", + "loc.input.help.dependency2Publisher": "O nome do editor da segunda dependência a ser incluída.", + "loc.input.label.dependency2Version": "Dependência 2: Versão", + "loc.input.help.dependency2Version": "O número da versão da segunda dependência a ser incluída.", + "loc.input.label.dependency2ProcessorArchitecture": "A dependência 2: Arquitetura do Processador", + "loc.input.help.dependency2ProcessorArchitecture": "A arquitetura de processador da segunda dependência a ser incluída.", + "loc.input.label.dependency2URI": "A dependência 2: URI", + "loc.input.help.dependency2URI": "O URI da segunda dependência a ser incluída.", + "loc.input.label.addDependency3": "Adicionar uma Terceira Dependência", + "loc.input.help.addDependency3": "Adicione-o uma terceira dependência.", + "loc.input.label.dependency3Name": "Dependência 3: Nome", + "loc.input.help.dependency3Name": "O nome da terceira dependência a ser incluída.", + "loc.input.label.dependency3Publisher": "Dependência 3: Fornecedor", + "loc.input.help.dependency3Publisher": "O nome do editor da terceira dependência a ser incluída.", + "loc.input.label.dependency3Version": "A dependência 3: Versão", + "loc.input.help.dependency3Version": "O número de versão da terceira dependência a ser incluída.", + "loc.input.label.dependency3ProcessorArchitecture": "Dependência 3: Arquitetura do Processador", + "loc.input.help.dependency3ProcessorArchitecture": "A arquitetura de processador da terceira dependência a ser incluída.", + "loc.input.label.dependency3URI": "A dependência 3: URI", + "loc.input.help.dependency3URI": "O URI da terceira dependência a ser incluída." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/ru-RU/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/ru-RU/resources.resjson new file mode 100644 index 000000000..f1d63953c --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/ru-RU/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "Файл установщика приложений для MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Создание или обновление файла установщика приложений для приложений MSIX", + "loc.instanceNameFormat": "Создание файла установщика приложений", + "loc.input.label.package": "Пакет", + "loc.input.help.package": "Путь к пакету или набору.", + "loc.input.label.outputPath": "Путь файла вывода", + "loc.input.help.outputPath": "Путь к установщику приложения, который нужно записать.", + "loc.input.label.method": "Метод создания файла установщика приложений", + "loc.input.help.method": "Метод, используемый для создания файла установщика приложения.", + "loc.input.label.existingFile": "Путь к существующему файлу установщика приложения", + "loc.input.help.existingFile": "Путь к существующему файлу установщика приложений для обновления.", + "loc.input.label.versionUpdateMethod": "Способ обновления версии файла установщика приложений", + "loc.input.help.versionUpdateMethod": "Увеличьте основную/вспомогательную/сборку/исправление файла установщика приложений или введите номер версии, который следует использовать.", + "loc.input.label.fileVersion": "Версия файла установщика приложений", + "loc.input.help.fileVersion": "Будет предоставлен номер версии. Необходимо сделать форму (основной). (минор). (Build). (редакция).", + "loc.input.label.uri": "Код URI", + "loc.input.help.uri": "Веб-код URI к перенаправленному файлу установщика приложений.", + "loc.input.label.mainItemUri": "Основной URI пакета/набора", + "loc.input.help.mainItemUri": "URI для расположения пакета/набора приложения.", + "loc.input.label.updateOnLaunch": "Обновление при запуске", + "loc.input.help.updateOnLaunch": "Установите приложение, чтобы проверить наличие обновлений при запуске.", + "loc.input.label.hoursBetweenUpdateChecks": "Количество часов между проверками обновления", + "loc.input.help.hoursBetweenUpdateChecks": "Как часто система будет проверять наличие обновлений для приложения.", + "loc.input.label.showPromptWhenUpdating": "Отображение интерфейса пользователю при обновлении", + "loc.input.help.showPromptWhenUpdating": "Отображение интерфейса для уведомления пользователя об обновлении при запуске приложения.", + "loc.input.label.updateBlocksActivation": "Обновление блокирует активацию приложения", + "loc.input.help.updateBlocksActivation": "Блокировать запуск приложения до завершения обновления.", + "loc.input.label.addOptionalItem1": "Добавить необязательный пакет/набор", + "loc.input.help.addOptionalItem1": "Добавить необязательный пакет или набор", + "loc.input.label.optionalItem1Name": "Необязательный элемент 1: имя", + "loc.input.help.optionalItem1Name": "Название пакета или набора вашего первого необязательного элемента для включения.", + "loc.input.label.optionalItem1Publisher": "Необязательный элемент 1: издатель", + "loc.input.help.optionalItem1Publisher": "Имя издателя первого необязательного элемента, которое нужно включить.", + "loc.input.label.optionalItem1Version": "Необязательный элемент 1: версия", + "loc.input.help.optionalItem1Version": "Номер версии первого необязательного элемента, который нужно включить.", + "loc.input.label.optionalItem1ProcessorArchitecture": "Необязательный элемент 1: архитектура процессора", + "loc.input.help.optionalItem1ProcessorArchitecture": "Архитектура процессора первого необязательного элемента, которую нужно включить.", + "loc.input.label.optionalItem1URI": "Необязательный элемент 1: URI", + "loc.input.help.optionalItem1URI": "Код URI первого необязательного элемента, который нужно включить.", + "loc.input.label.addOptionalItem2": "Добавить второй необязательный пакет/набор", + "loc.input.help.addOptionalItem2": "Добавить второй необязательный пакет или набор", + "loc.input.label.optionalItem2Name": "Необязательный элемент 2: имя", + "loc.input.help.optionalItem2Name": "Название пакета или набора вашего второго необязательного элемента для включения.", + "loc.input.label.optionalItem2Publisher": "Необязательный элемент 2: издатель", + "loc.input.help.optionalItem2Publisher": "Имя издателя второго необязательного элемента, которое нужно включить.", + "loc.input.label.optionalItem2Version": "Необязательный элемент 2: версия", + "loc.input.help.optionalItem2Version": "Номер версии второго необязательного элемента, который нужно включить.", + "loc.input.label.optionalItem2ProcessorArchitecture": "Необязательный элемент 2: архитектура процессора", + "loc.input.help.optionalItem2ProcessorArchitecture": "Архитектура процессора второго необязательного элемента, которую нужно включить.", + "loc.input.label.optionalItem2URI": "Необязательный элемент 2: URI", + "loc.input.help.optionalItem2URI": "Код URI второго необязательного элемента, который нужно включить.", + "loc.input.label.addOptionalItem3": "Добавить третий необязательный пакет/набор", + "loc.input.help.addOptionalItem3": "Добавить третий необязательный пакет или набор", + "loc.input.label.optionalItem3Name": "Необязательный элемент 3: имя", + "loc.input.help.optionalItem3Name": "Название пакета или набора вашего третьего необязательного элемента для включения.", + "loc.input.label.optionalItem3Publisher": "Необязательный элемент 3: издатель", + "loc.input.help.optionalItem3Publisher": "Имя издателя третьего необязательного элемента, которое нужно включить.", + "loc.input.label.optionalItem3Version": "Необязательный элемент 3: версия", + "loc.input.help.optionalItem3Version": "Номер версии третьего необязательного элемента, который нужно включить.", + "loc.input.label.optionalItem3ProcessorArchitecture": "Необязательный элемент 3: архитектура процессора", + "loc.input.help.optionalItem3ProcessorArchitecture": "Архитектура процессора третьего необязательного элемента, которую нужно включить.", + "loc.input.label.optionalItem3URI": "Необязательный элемент 3: URI", + "loc.input.help.optionalItem3URI": "Код URI третьего необязательного элемента, который нужно включить.", + "loc.input.label.addDependency1": "Добавить зависимость", + "loc.input.help.addDependency1": "Добавьте зависимость.", + "loc.input.label.dependency1Name": "Зависимость 1: имя", + "loc.input.help.dependency1Name": "Имя первой зависимости, которое нужно включить.", + "loc.input.label.dependency1Publisher": "Зависимость 1: издатель", + "loc.input.help.dependency1Publisher": "Имя издателя первой зависимости для включения.", + "loc.input.label.dependency1Version": "Зависимость 1: версия", + "loc.input.help.dependency1Version": "Номер версии первой зависимости, который нужно включить.", + "loc.input.label.dependency1ProcessorArchitecture": "Зависимость 1: архитектура процессора", + "loc.input.help.dependency1ProcessorArchitecture": "Архитектура процессора первой зависимости, которую нужно включить.", + "loc.input.label.dependency1URI": "Зависимость 1: URI", + "loc.input.help.dependency1URI": "Код URI первой зависимости, который нужно включить.", + "loc.input.label.addDependency2": "Добавление второй зависимости", + "loc.input.help.addDependency2": "Добавьте вторую зависимость.", + "loc.input.label.dependency2Name": "Зависимость 2: имя", + "loc.input.help.dependency2Name": "Имя второй зависимости, которое нужно включить.", + "loc.input.label.dependency2Publisher": "Зависимость 2: издатель", + "loc.input.help.dependency2Publisher": "Имя издателя второй зависимости, которое нужно включить.", + "loc.input.label.dependency2Version": "Зависимость 2: версия", + "loc.input.help.dependency2Version": "Номер версии второй зависимости, который нужно включить.", + "loc.input.label.dependency2ProcessorArchitecture": "Зависимость 2: архитектура процессора", + "loc.input.help.dependency2ProcessorArchitecture": "Архитектура процессора второй зависимости, которую нужно включить.", + "loc.input.label.dependency2URI": "Зависимость 2: URI", + "loc.input.help.dependency2URI": "Код URI второй зависимости, который нужно включить.", + "loc.input.label.addDependency3": "Добавление третьей зависимости", + "loc.input.help.addDependency3": "Добавьте третью зависимость.", + "loc.input.label.dependency3Name": "Зависимость 3: имя", + "loc.input.help.dependency3Name": "Имя третьей зависимости, которое нужно включить.", + "loc.input.label.dependency3Publisher": "Зависимость 3: издатель", + "loc.input.help.dependency3Publisher": "Имя издателя третьей зависимости, которое нужно включить.", + "loc.input.label.dependency3Version": "Зависимость 3: версия", + "loc.input.help.dependency3Version": "Номер версии третьей зависимости, который нужно включить.", + "loc.input.label.dependency3ProcessorArchitecture": "Зависимость 3: архитектура процессора", + "loc.input.help.dependency3ProcessorArchitecture": "Архитектура процессора третьей зависимости, которую нужно включить.", + "loc.input.label.dependency3URI": "Зависимость 3: URI", + "loc.input.help.dependency3URI": "Код URI третьей зависимости, которое нужно включить." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/zh-CN/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/zh-CN/resources.resjson new file mode 100644 index 000000000..8eb1b62d8 --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/zh-CN/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "MSIX 的应用安装程序文件", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "创建或更新 MSIX 应用的应用安装程序文件", + "loc.instanceNameFormat": "创建应用安装程序文件", + "loc.input.label.package": "程序包", + "loc.input.help.package": "程序包或捆绑包的路径。", + "loc.input.label.outputPath": "输出文件路径", + "loc.input.help.outputPath": "要写入的应用安装程序的路径。", + "loc.input.label.method": "创建应用安装程序文件的方法", + "loc.input.help.method": "用于创建应用安装程序文件的方法。", + "loc.input.label.existingFile": "现有应用安装程序文件的路径", + "loc.input.help.existingFile": "要更新的现有应用安装程序文件的路径。", + "loc.input.label.versionUpdateMethod": "用于更新应用安装程序文件版本的方法", + "loc.input.help.versionUpdateMethod": "递增应用程序安装文件的 major/minor/build/revision 或输入要使用的版本号。", + "loc.input.label.fileVersion": "应用安装程序文件的版本", + "loc.input.help.fileVersion": "将提供的版本号。必须采用 (major).(minor).(build).(revision) 的形式。", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "重定向应用程序安装程序文件的 Web URI。", + "loc.input.label.mainItemUri": "主程序包/捆绑包 URI", + "loc.input.help.mainItemUri": "应用程序包/捆绑包位置的 URI。", + "loc.input.label.updateOnLaunch": "发布时更新", + "loc.input.help.updateOnLaunch": "将应用设置为启动时检查更新。", + "loc.input.label.hoursBetweenUpdateChecks": "更新检查之间的小时数", + "loc.input.help.hoursBetweenUpdateChecks": "系统检查应用程序更新的频率。", + "loc.input.label.showPromptWhenUpdating": "更新时向用户显示 UI", + "loc.input.help.showPromptWhenUpdating": "显示一个 UI,以便在启动应用程序进行更新时通知用户。", + "loc.input.label.updateBlocksActivation": "更新 Blocks 应用激活", + "loc.input.help.updateBlocksActivation": "阻止该应用程序启动,直到更新完成。", + "loc.input.label.addOptionalItem1": "添加可选程序包/捆绑包", + "loc.input.help.addOptionalItem1": "添加可选程序包或捆绑包", + "loc.input.label.optionalItem1Name": "可选项目1:名称", + "loc.input.help.optionalItem1Name": "要包含的第一个可选项目的程序包或捆绑包名称。", + "loc.input.label.optionalItem1Publisher": "可选项目1: Publisher", + "loc.input.help.optionalItem1Publisher": "要包含的第一个可选项的发布者名称。", + "loc.input.label.optionalItem1Version": "可选项目1:版本", + "loc.input.help.optionalItem1Version": "要包含的第一个可选项的版本号。", + "loc.input.label.optionalItem1ProcessorArchitecture": "可选项目1:处理器体系结构", + "loc.input.help.optionalItem1ProcessorArchitecture": "要包含的第一个可选项的处理器体系结构。", + "loc.input.label.optionalItem1URI": "可选项目1: URI", + "loc.input.help.optionalItem1URI": "要包含的第一个可选项的 URI。", + "loc.input.label.addOptionalItem2": "添加第二个可选程序包/捆绑包", + "loc.input.help.addOptionalItem2": "添加第二个可选程序包或捆绑包", + "loc.input.label.optionalItem2Name": "可选项目2:名称", + "loc.input.help.optionalItem2Name": "要包含的第二个可选项目的程序包或捆绑包名称。", + "loc.input.label.optionalItem2Publisher": "可选项目2: Publisher", + "loc.input.help.optionalItem2Publisher": "要包含的第二个可选项的发布者名称。", + "loc.input.label.optionalItem2Version": "可选项目2:版本", + "loc.input.help.optionalItem2Version": "要包含的第二个可选项的版本号。", + "loc.input.label.optionalItem2ProcessorArchitecture": "可选项目2:处理器体系结构", + "loc.input.help.optionalItem2ProcessorArchitecture": "要包含的第二个可选项的处理器体系结构。", + "loc.input.label.optionalItem2URI": "可选项目2: URI", + "loc.input.help.optionalItem2URI": "要包含的第二个可选项的 URI。", + "loc.input.label.addOptionalItem3": "添加第三个可选程序包/捆绑包", + "loc.input.help.addOptionalItem3": "添加第三个可选程序包或捆绑包", + "loc.input.label.optionalItem3Name": "可选项目3:名称", + "loc.input.help.optionalItem3Name": "要包含的第三个可选项目的程序包或捆绑包名称。", + "loc.input.label.optionalItem3Publisher": "可选项目3: Publisher", + "loc.input.help.optionalItem3Publisher": "要包含的第三个可选项的发布者名称。", + "loc.input.label.optionalItem3Version": "可选项目3:版本", + "loc.input.help.optionalItem3Version": "要包含的第三个可选项的版本号。", + "loc.input.label.optionalItem3ProcessorArchitecture": "可选项目3:处理器体系结构", + "loc.input.help.optionalItem3ProcessorArchitecture": "要包含的第三个可选项的处理器体系结构。", + "loc.input.label.optionalItem3URI": "可选项目3: URI", + "loc.input.help.optionalItem3URI": "要包含的第三个可选项的 URI。", + "loc.input.label.addDependency1": "添加依赖项", + "loc.input.help.addDependency1": "添加依赖项。", + "loc.input.label.dependency1Name": "依赖项 1:名称", + "loc.input.help.dependency1Name": "要包含的第一个依赖项的名称。", + "loc.input.label.dependency1Publisher": "依赖项 1: Publisher", + "loc.input.help.dependency1Publisher": "要包含的第一个依赖项的发布者名称。", + "loc.input.label.dependency1Version": "依赖项 1:版本", + "loc.input.help.dependency1Version": "要包含的第一个依赖项的版本号。", + "loc.input.label.dependency1ProcessorArchitecture": "依赖项 1:处理器体系结构", + "loc.input.help.dependency1ProcessorArchitecture": "要包含的第一个依赖项的处理器体系结构。", + "loc.input.label.dependency1URI": "依赖项 1: URI", + "loc.input.help.dependency1URI": "要包含的第一个依赖项的 URI。", + "loc.input.label.addDependency2": "添加第二个依赖项", + "loc.input.help.addDependency2": "添加第二个依赖项。", + "loc.input.label.dependency2Name": "依赖项 2:名称", + "loc.input.help.dependency2Name": "要包含的第二个依赖项的名称。", + "loc.input.label.dependency2Publisher": "依赖项 2: Publisher", + "loc.input.help.dependency2Publisher": "要包含的第二个依赖项的发布者名称。", + "loc.input.label.dependency2Version": "依赖项 2:版本", + "loc.input.help.dependency2Version": "要包含的第二个相关性的版本号。", + "loc.input.label.dependency2ProcessorArchitecture": "依赖项 2:处理器体系结构", + "loc.input.help.dependency2ProcessorArchitecture": "要包括的第二个依赖项的处理器体系结构。", + "loc.input.label.dependency2URI": "依赖项 2: URI", + "loc.input.help.dependency2URI": "要包含的第二个依赖项的 URI。", + "loc.input.label.addDependency3": "添加第三个依赖项", + "loc.input.help.addDependency3": "添加第三个依赖项。", + "loc.input.label.dependency3Name": "依赖项 3:名称", + "loc.input.help.dependency3Name": "要包含的第三个依赖项的名称。", + "loc.input.label.dependency3Publisher": "依赖项 3:Publisher", + "loc.input.help.dependency3Publisher": "要包含的第三个依赖项的发布者名称。", + "loc.input.label.dependency3Version": "依赖项 3:版本", + "loc.input.help.dependency3Version": "第三个要包含的相关性的版本号。", + "loc.input.label.dependency3ProcessorArchitecture": "依赖项 3:处理器体系结构", + "loc.input.help.dependency3ProcessorArchitecture": "要包括的第三个依赖项的处理器体系结构。", + "loc.input.label.dependency3URI": "依赖项 3: URI", + "loc.input.help.dependency3URI": "要包含的第三个依赖项的 URI。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/zh-TW/resources.resjson b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/zh-TW/resources.resjson new file mode 100644 index 000000000..e16c36ddc --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/Strings/resources.resjson/zh-TW/resources.resjson @@ -0,0 +1,102 @@ +{ + "loc.friendlyName": "適用於 MSIX 的應用程式安裝程式檔案", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "建立或更新 MSIX 應用程式的應用程式安裝程式檔案", + "loc.instanceNameFormat": "建立應用程式安裝程式檔案", + "loc.input.label.package": "套件", + "loc.input.help.package": "套件或套件組合的路徑。", + "loc.input.label.outputPath": "輸出檔案路徑", + "loc.input.help.outputPath": "要寫入的應用程式安裝程式路徑。", + "loc.input.label.method": "建立應用程式安裝程式檔案的方法", + "loc.input.help.method": "用來建立應用程式安裝程式檔案的方法。", + "loc.input.label.existingFile": "現有應用程式安裝程式檔案的路徑", + "loc.input.help.existingFile": "要更新的現有應用程式安裝程式檔案路徑。", + "loc.input.label.versionUpdateMethod": "更新應用程式安裝程式檔案版本的方法", + "loc.input.help.versionUpdateMethod": "增加應用程式安裝程式檔案的主要/次要/組建/修訂,或輸入要使用的版本號碼。", + "loc.input.label.fileVersion": "應用程式安裝程式檔案的版本", + "loc.input.help.fileVersion": "將提供的版本號碼。必須採取的格式為 (主要).(次要).(組建).(修訂)。", + "loc.input.label.uri": "URI", + "loc.input.help.uri": "重新導向應用程式安裝程式檔案的 Web URI。", + "loc.input.label.mainItemUri": "主套件/套件組合 URI", + "loc.input.help.mainItemUri": "前往應用程式套件/套件組合位置的 URI。", + "loc.input.label.updateOnLaunch": "啟動時更新", + "loc.input.help.updateOnLaunch": "設定啟動時要檢查更新的應用程式。", + "loc.input.label.hoursBetweenUpdateChecks": "更新檢查間隔 (小時)", + "loc.input.help.hoursBetweenUpdateChecks": "系統檢查應用程式更新的頻率。", + "loc.input.label.showPromptWhenUpdating": "正在更新時向使用者顯示 UI", + "loc.input.help.showPromptWhenUpdating": "顯示 UI 以在啟動應用程式時發生更新時通知使用者。", + "loc.input.label.updateBlocksActivation": "更新封鎖應用程式啟用", + "loc.input.help.updateBlocksActivation": "封鎖此應用程式的啟動,直到更新完成。", + "loc.input.label.addOptionalItem1": "新增選擇性套件/套件組合", + "loc.input.help.addOptionalItem1": "新增選擇性套件或套件組合", + "loc.input.label.optionalItem1Name": "選擇性項目 1: 名稱", + "loc.input.help.optionalItem1Name": "要包括的第一個選擇性項目的套件或套件組合名稱。", + "loc.input.label.optionalItem1Publisher": "選擇性項目 1: 發行者", + "loc.input.help.optionalItem1Publisher": "要包含的第一個選擇性專案的發行者名稱。", + "loc.input.label.optionalItem1Version": "選擇性項目 1: 版本", + "loc.input.help.optionalItem1Version": "要包含的第一個選擇性專案的版本號碼。", + "loc.input.label.optionalItem1ProcessorArchitecture": "選擇性項目 1: 處理器架構", + "loc.input.help.optionalItem1ProcessorArchitecture": "要包含的第一個選擇性專案的處理器架構。", + "loc.input.label.optionalItem1URI": "選擇性項目 1: URI", + "loc.input.help.optionalItem1URI": "要包含之第一個選擇性項目的 URI。", + "loc.input.label.addOptionalItem2": "新增第二個選擇性套件/套件組合", + "loc.input.help.addOptionalItem2": "新增第二個選擇性套件或套件組合", + "loc.input.label.optionalItem2Name": "選擇性項目 2: 名稱", + "loc.input.help.optionalItem2Name": "要包括的第二個選擇性項目的套件或套件組合名稱。", + "loc.input.label.optionalItem2Publisher": "選擇性項目 2: 發行者", + "loc.input.help.optionalItem2Publisher": "要包含之第二個選擇性項目的發行者名稱。", + "loc.input.label.optionalItem2Version": "選擇性項目 2: 版本", + "loc.input.help.optionalItem2Version": "要包含的第二個選用專案的版本號碼。", + "loc.input.label.optionalItem2ProcessorArchitecture": "選擇性項目 2: 處理器架構", + "loc.input.help.optionalItem2ProcessorArchitecture": "要包含之第二個選擇性項目的處理器架構。", + "loc.input.label.optionalItem2URI": "選擇性項目 2: URI", + "loc.input.help.optionalItem2URI": "要包含之第二個選擇性項目的 URI。", + "loc.input.label.addOptionalItem3": "新增第三個選擇性套件/套件組合", + "loc.input.help.addOptionalItem3": "新增第三個選擇性套件或套件組合", + "loc.input.label.optionalItem3Name": "選擇性項目 3: 名稱", + "loc.input.help.optionalItem3Name": "要包括的第三個選擇性項目的套件或套件組合名稱。", + "loc.input.label.optionalItem3Publisher": "選擇性項目 3: 發行者", + "loc.input.help.optionalItem3Publisher": "要包含第三個選擇性專案的發行者名稱。", + "loc.input.label.optionalItem3Version": "選擇性項目 3: 版本", + "loc.input.help.optionalItem3Version": "要包含的第三個選用專案的版本號碼。", + "loc.input.label.optionalItem3ProcessorArchitecture": "選擇性項目 3: 處理器架構", + "loc.input.help.optionalItem3ProcessorArchitecture": "要包含第三個選擇性專案的處理器架構。", + "loc.input.label.optionalItem3URI": "選擇性項目 3: URI", + "loc.input.help.optionalItem3URI": "要包含之第三個選擇性項目的 URI。", + "loc.input.label.addDependency1": "新增相依性", + "loc.input.help.addDependency1": "新增相依性。", + "loc.input.label.dependency1Name": "相依性 1: 名稱", + "loc.input.help.dependency1Name": "要包含的第一個相依性名稱。", + "loc.input.label.dependency1Publisher": "相依性 1: 發行者", + "loc.input.help.dependency1Publisher": "要包含之第一個相依性的發行者名稱。", + "loc.input.label.dependency1Version": "相依性 1: 版本", + "loc.input.help.dependency1Version": "要包含的第一個相依性版本號碼。", + "loc.input.label.dependency1ProcessorArchitecture": "相依性 1: 處理器架構", + "loc.input.help.dependency1ProcessorArchitecture": "要包含之第一個相依性的處理器架構。", + "loc.input.label.dependency1URI": "相依性 1: URI", + "loc.input.help.dependency1URI": "要包含之第一個相依性的 URI。", + "loc.input.label.addDependency2": "新增第二個相依性", + "loc.input.help.addDependency2": "新增第二個相依性。", + "loc.input.label.dependency2Name": "相依性 2: 名稱", + "loc.input.help.dependency2Name": "要包含之第二個相依性的名稱。", + "loc.input.label.dependency2Publisher": "相依性 2: 發行者", + "loc.input.help.dependency2Publisher": "要包含之第二個相依性的發行者名稱。", + "loc.input.label.dependency2Version": "相依性 2: 版本", + "loc.input.help.dependency2Version": "要包含之第二個相依性的版本號碼。", + "loc.input.label.dependency2ProcessorArchitecture": "相依性 2: 處理器架構", + "loc.input.help.dependency2ProcessorArchitecture": "第二個相依性的處理器架構。", + "loc.input.label.dependency2URI": "相依性 2: URI", + "loc.input.help.dependency2URI": "要包含之第二個相依性的 URI。", + "loc.input.label.addDependency3": "新增第三個相依性", + "loc.input.help.addDependency3": "新增第三個相依性。", + "loc.input.label.dependency3Name": "相依性 3: 名稱", + "loc.input.help.dependency3Name": "要包含之第三個相依性的名稱。", + "loc.input.label.dependency3Publisher": "相依性 3: 發行者", + "loc.input.help.dependency3Publisher": "要包含之第三個相依性的發行者名稱。", + "loc.input.label.dependency3Version": "相依性 3: 版本", + "loc.input.help.dependency3Version": "要包含之第三個相依性的版本號碼。", + "loc.input.label.dependency3ProcessorArchitecture": "相依性 3: 處理器架構", + "loc.input.help.dependency3ProcessorArchitecture": "要包含之第三個相依性的處理器架構。", + "loc.input.label.dependency3URI": "相依性 3: URI", + "loc.input.help.dependency3URI": "要包含之第三個相依性的 URI。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/appinstallerfile.ts b/tools/pipelines-tasks/AppInstallerFile/appinstallerfile.ts new file mode 100644 index 000000000..420667e3f --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/appinstallerfile.ts @@ -0,0 +1,186 @@ +import helpers = require('common/helpers'); + +/* This file contains helpers for creating or modifying an .appinstaller file */ + +export interface UpdateParameters +{ + existingFile: string, + versionUpdateMethod: string, + fileVersion?: string +} + +export interface CreateParameters +{ + uri: string, + fileVersion: string, + mainItemUri: string, + updateOnLaunch: boolean, + hoursBetweenUpdateChecks?: string, + showPromptWhenUpdating?: boolean, + updateBlocksActivation?: boolean, + optionalItems: PackageOrBundle[], + dependencies: PackageOrBundle[] +} + +// Identity information about a package and bundle. +// This can represent a main item, an optional item or a dependency. +// The inner property is used to produce the XML, so the names of the +// properties must be kept named exactly as they are. +export interface PackageOrBundle +{ + isBundle: boolean, + packageOrBundle: + { + Name: string, + Publisher: string, + Version: string, + ProcessorArchitecture?: string, + Uri: string + } +} + +/** + * Update the version of the App Installer file. + * Save the new App Installer file to outputPath. + * @param outputPath the path to save the new App Installer content to + * @param existingFile the path to the existing App Installer file that needs updating + * @param versionUpdateMethod how to update the version of the file + * @param fileVersion new version of the file, if the update method is manual + * @param mainApp identity of the main app, used to update the version + */ +export const updateExisting = async ( + outputPath: string, + { + existingFile, + versionUpdateMethod, + fileVersion + }: UpdateParameters, + mainApp: PackageOrBundle) => +{ + const appInstallerFile = await helpers.parseXml(existingFile); + + let newFileVersion: string; + if (versionUpdateMethod !== 'manual') + { + const existingFileVersion: string[] = appInstallerFile.AppInstaller.$.Version.split('.'); + newFileVersion = helpers.incrementVersion(existingFileVersion, versionUpdateMethod); + } + else + { + newFileVersion = fileVersion!; + } + + // Update the version of the file. + appInstallerFile.AppInstaller.$.Version = newFileVersion; + + // Update the version of the main app + const tagToEdit: string = mainApp.isBundle ? 'MainBundle' : 'MainPackage'; + appInstallerFile.AppInstaller[tagToEdit][0].$.Version = mainApp.packageOrBundle.Version; + + helpers.writeXml(appInstallerFile, outputPath); +} + +const getXmlElementsForItems = (items: PackageOrBundle[]): any => +{ + const packages: any[] = []; + const bundles: any[] = []; + + for (const item of items) + { + // The properties of the package/bundle are stored as attributes in the XML + const xmlItem = { $: item.packageOrBundle }; + if (item.isBundle) + { + bundles.push(xmlItem); + } + else + { + packages.push(xmlItem); + } + } + + const element: any = {}; + if (bundles.length > 0) + { + element.Bundle = bundles; + } + + if (packages.length > 0) + { + element.Package = packages; + } + + return element; +} + +export const createNew = ( + outputPath:string, + { + fileVersion, + uri, + optionalItems, + dependencies, + updateOnLaunch, + hoursBetweenUpdateChecks, + showPromptWhenUpdating, + updateBlocksActivation + }: CreateParameters, + mainApp: PackageOrBundle) => +{ + // Object representing the new XML file. + // All of the property names here must be keep exactly as they are, as they will be translated to XML. + // Initialize it with the root element. + const newFile: any = { AppInstaller: {} }; + + // Attributes of + newFile.AppInstaller.$ = { + xmlns: 'http://schemas.microsoft.com/appx/appinstaller/2018', + Version: fileVersion, + Uri: uri + }; + + // MainPackage/MainBundle + newFile.AppInstaller[mainApp.isBundle ? 'MainBundle' : 'MainPackage'] = + { + $: mainApp.packageOrBundle + } + + // Optional items + if (optionalItems.length > 0) + { + newFile.AppInstaller.OptionalPackages = getXmlElementsForItems(optionalItems); + } + + // Dependencies + if (dependencies.length > 0) + { + newFile.AppInstaller.Dependencies = getXmlElementsForItems(dependencies); + } + + // Update settings + if (updateOnLaunch) + { + const onLaunchSettings: any = { $: {} }; + newFile.AppInstaller.UpdateSettings = + { + OnLaunch: onLaunchSettings + }; + + if (hoursBetweenUpdateChecks) + { + onLaunchSettings.$.HoursBetweenUpdateChecks = hoursBetweenUpdateChecks; + } + + if (showPromptWhenUpdating) + { + onLaunchSettings.$.ShowPrompt = showPromptWhenUpdating; + } + + if (updateBlocksActivation) + { + onLaunchSettings.$.UpdateBlocksActivation = updateBlocksActivation; + } + } + + helpers.writeXml(newFile, outputPath); +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/debug_inputs.env b/tools/pipelines-tasks/AppInstallerFile/debug_inputs.env new file mode 100644 index 000000000..75f9bdbdd --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/debug_inputs.env @@ -0,0 +1,32 @@ +# Set the task's inputs here to use when debugging + +INPUT_PACKAGE=test\assets\existingPackage.msix +INPUT_OUTPUTPATH=debug\app.appinstaller +INPUT_EXISTINGFILE=test\assets\existingAppInstallerFile.appinstaller +INPUT_METHOD=create +INPUT_VERSIONUPDATEMETHOD=major +INPUT_FILEVERSION=1.0.0.1 +INPUT_URI=https://example.com +INPUT_MAINITEMURI=https://example.com + +INPUT_UPDATEONLAUNCH=true +INPUT_HOURSBETWEENUPDATECHECKS=24 + +INPUT_ADDOPTIONALITEM1=package +INPUT_OPTIONALITEM1NAME=OptionalItem1 +INPUT_OPTIONALITEM1PUBLISHER=CN=Publisher1 +INPUT_OPTIONALITEM1VERSION=1.0.0.0 +INPUT_OPTIONALITEM1PROCESSORARCHITECTURE=x86 +INPUT_OPTIONALITEM1URI=https://example.com +INPUT_ADDOPTIONALITEM2=bundle +INPUT_OPTIONALITEM2NAME=OptionalItem2 +INPUT_OPTIONALITEM2PUBLISHER=CN=Publisher2 +INPUT_OPTIONALITEM2VERSION=2.0.0.0 +INPUT_OPTIONALITEM2URI=https://example.com +INPUT_ADDOPTIONALITEM3=bundle +INPUT_OPTIONALITEM3NAME=OptionalItem2 +INPUT_OPTIONALITEM3PUBLISHER=CN=Publisher3 +INPUT_OPTIONALITEM3VERSION=3.0.0.0 +INPUT_OPTIONALITEM3URI=https://example.com + +INPUT_ADDDEPENDENCY1=none \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/icon.png b/tools/pipelines-tasks/AppInstallerFile/icon.png new file mode 100644 index 000000000..052291f00 Binary files /dev/null and b/tools/pipelines-tasks/AppInstallerFile/icon.png differ diff --git a/tools/pipelines-tasks/AppInstallerFile/index.ts b/tools/pipelines-tasks/AppInstallerFile/index.ts new file mode 100644 index 000000000..c08bb67dd --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/index.ts @@ -0,0 +1,217 @@ +import path = require('path'); +import tl = require('azure-pipelines-task-lib/task') + +import helpers = require('common/helpers'); + +import appInstallerFile = require('./appinstallerfile'); + +interface Inputs { + packagePath: string, + outputPath: string, + method: string, + inputsForUpdate?: appInstallerFile.UpdateParameters, + inputsForCreate?: appInstallerFile.CreateParameters +} + +const readInputsForItem = (inputPrefix: string, isBundle: boolean): appInstallerFile.PackageOrBundle => +{ + const Name: string = helpers.getInputWithErrorCheck(`${inputPrefix}Name`, `Input Error: ${inputPrefix} missing name.`); + const Publisher: string = helpers.getInputWithErrorCheck(`${inputPrefix}Publisher`, `Input Error: ${inputPrefix} missing publisher.`); + const Version: string = helpers.getInputWithErrorCheck(`${inputPrefix}Version`, `Input Error: ${inputPrefix} missing version.`); + const ProcessorArchitecture: string | undefined = tl.getInput(`${inputPrefix}ProcessorArchitecture`); + const Uri: string = helpers.getInputWithErrorCheck(`${inputPrefix}URI`, `Input Error: ${inputPrefix} missing URI.`); + + return { isBundle, packageOrBundle: { Name, Publisher, Version, ProcessorArchitecture, Uri } }; +} + +const readInputsForCreate = (): appInstallerFile.CreateParameters => +{ + const uri: string = helpers.getInputWithErrorCheck('uri', 'The App Installer file URI is required but none was given'); + const fileVersion: string = helpers.getInputWithErrorCheck('fileVersion', 'The App Installer file version is required but none was given'); + const mainItemUri: string = helpers.getInputWithErrorCheck('mainItemUri', 'The URI of the main package/bundle is required but none was given'); + + const updateOnLaunch: boolean = tl.getBoolInput('updateOnLaunch'); + let hoursBetweenUpdateChecks: string | undefined; + let showPromptWhenUpdating: boolean | undefined; + let updateBlocksActivation: boolean | undefined; + if (updateOnLaunch) + { + hoursBetweenUpdateChecks = tl.getInput('hoursBetweenUpdateChecks'); + showPromptWhenUpdating = tl.getBoolInput('showPromptWhenUpdating'); + if (showPromptWhenUpdating) + { + updateBlocksActivation = tl.getBoolInput('updateBlocksActivation'); + } + } + + const optionalItems: appInstallerFile.PackageOrBundle[] = []; + const maximumOptionalItems: number = 3; + for (let i = 1; i <= maximumOptionalItems; i++) + { + const optionalItemType: string | undefined = tl.getInput(`addOptionalItem${i}`); + const optionalItemInputPrefix: string = `optionalItem${i}`; + if (optionalItemType === 'package') + { + optionalItems.push(readInputsForItem(optionalItemInputPrefix, /* isBundle */ false)); + } + else if (optionalItemType === 'bundle') + { + optionalItems.push(readInputsForItem(optionalItemInputPrefix, /* isBundle */ true)); + } + else + { + break; + } + } + + const dependencies: appInstallerFile.PackageOrBundle[] = []; + const maximumDependencies: number = 3; + for (let i = 1; i <= maximumDependencies; i++) + { + const dependencyType: string | undefined = tl.getInput(`addDependency${i}`); + const dependencyInputPrefix: string = `dependency${i}`; + if (dependencyType === 'package') + { + dependencies.push(readInputsForItem(dependencyInputPrefix, /* isBundle */ false)); + } + else if (dependencyType === 'bundle') + { + dependencies.push(readInputsForItem(dependencyInputPrefix, /* isBundle */ true)); + } + } + + return { + uri, + fileVersion, + mainItemUri, + updateOnLaunch, + hoursBetweenUpdateChecks, + showPromptWhenUpdating, + updateBlocksActivation, + optionalItems, + dependencies + } +} + +const readInputsForUpdate = (): appInstallerFile.UpdateParameters => +{ + const existingFile: string = helpers.getInputWithErrorCheck('existingFile', 'The path to the existing App Installer file that requires updating is required, but none was given.'); + const versionUpdateMethod: string = helpers.getInputWithErrorCheck('versionUpdateMethod', 'An update method is required to update the version but none was given.'); + let fileVersion: string | undefined; + if (versionUpdateMethod === 'manual') + { + fileVersion = helpers.getInputWithErrorCheck('fileVersionUpdateMethod', 'To manually update the version, a version is needed but none was given.'); + } + + return { existingFile, versionUpdateMethod, fileVersion }; +} + +/** + * @returns A dictionary with the task arguments. + */ +const readInputs = (): Inputs => +{ + const packagePath: string = helpers.getInputWithErrorCheck('package', 'Path to the package or bundle is required'); + const outputPath: string = helpers.getInputWithErrorCheck('outputPath', 'A name is required to save the App Installer to once created, but none was given'); + const method: string = helpers.getInputWithErrorCheck('method', 'Method for generating the App Installer file is required.') + let inputsForCreate: appInstallerFile.CreateParameters | undefined; + let inputsForUpdate: appInstallerFile.UpdateParameters | undefined; + + if (method === 'create') + { + inputsForCreate = readInputsForCreate(); + } + else if (method === 'update') + { + inputsForUpdate = readInputsForUpdate(); + } + + return { packagePath, outputPath, method, inputsForCreate, inputsForUpdate }; +} + +/** + * Returns the parsed manifest (appxmanifest.xml for a package or appxbundlemanifest.xml + * for a bundle) by unpacking the package or bundle. This will unpack it, parse the + * manifest and delete the unpack directory. + * @param packagePath path to the package to inspect + * @param isBundle whether or not the given App is a bundle + */ +const getAppxManifestPathFromApp = async (packagePath: string, isBundle: boolean): Promise => +{ + const packageName: string = path.parse(packagePath).name; + const extractionDirectory: string = path.join(helpers.getTempDirectory(), packageName); + try + { + tl.debug(`Unpacking ${packagePath} to `); + await helpers.unpackFile(packagePath, extractionDirectory, isBundle); + let appxManifestPath: string; + + // here, we want to want to detect if the given app + // is an MSIX package or bundle, specifically, if it's a bundle, + // we need to look for a folder called AppxMetadata which should + // contain the appxbundlemanifest.xml file + if (isBundle) + { + appxManifestPath = path.join(extractionDirectory, 'AppxMetadata', 'AppxBundleManifest.xml'); + } + else + { + // if it's just a package then the directory which we unpacked the + // content to should contain the appxmanifest.xml file + appxManifestPath = path.join(extractionDirectory, 'AppxManifest.xml'); + } + + return await helpers.parseXml(appxManifestPath); + } + finally + { + tl.rmRF(extractionDirectory); + } +} + +const getAppInformationFromPackage = async (packagePath: string): Promise => +{ + const extension: string = path.extname(packagePath).toLowerCase(); + const isBundle: boolean = extension === '.appxbundle' || extension === '.msixbundle'; + + // Read the manifest + const manifest = await getAppxManifestPathFromApp(packagePath, isBundle); + + if (isBundle) + { + return { isBundle, packageOrBundle: manifest.Bundle.Identity[0].$ }; + } + else + { + return { isBundle, packageOrBundle: manifest.Package.Identity[0].$ }; + } +} + +/** + * Main function for the task. + */ +const run = async () => +{ + tl.setResourcePath(path.join(__dirname, 'task.json')); + + const inputs: Inputs = readInputs(); + + // Get app info from the manifest in the app to use in the App Installer file + const appInformation: appInstallerFile.PackageOrBundle = await getAppInformationFromPackage(inputs.packagePath); + + if (inputs.method === 'create') + { + // The main item was read from the package and does not yet have an URI + appInformation.packageOrBundle.Uri = inputs.inputsForCreate!.mainItemUri; + appInstallerFile.createNew(inputs.outputPath, inputs.inputsForCreate!, appInformation); + } + else if (inputs.method === 'update') + { + await appInstallerFile.updateExisting(inputs.outputPath, inputs.inputsForUpdate!, appInformation); + } +} + +run().catch(err => + { + tl.setResult(tl.TaskResult.Failed, err.message); + }) \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/package-lock.json b/tools/pipelines-tasks/AppInstallerFile/package-lock.json new file mode 100644 index 000000000..f853a42bb --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/package-lock.json @@ -0,0 +1,340 @@ +{ + "name": "msix-tasks-app-installer-file", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "14.14.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz", + "integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw==" + }, + "@types/xml2js": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.8.tgz", + "integrity": "sha512-EyvT83ezOdec7BhDaEcsklWy7RSIdi6CNe95tmOAK0yx/Lm30C9K75snT3fYayK59ApC2oyW+rcHErdG05FHJA==", + "requires": { + "@types/node": "*" + } + }, + "adm-zip": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "azure-devops-node-api": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-10.2.2.tgz", + "integrity": "sha512-4TVv2X7oNStT0vLaEfExmy3J4/CzfuXolEcQl/BRUmvGySqKStTG2O55/hUQ0kM7UJlZBLgniM0SBq4d/WkKow==", + "requires": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "azure-pipelines-task-lib": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.12.2.tgz", + "integrity": "sha512-ofAdVZcL90Qv6zYcKa1vK3Wnrl2kxoKX/Idvb7RWrqHQzcJlAEjCU4UCB5y6NnSKqRSyVTIhdS6hChphpOaiMQ==", + "requires": { + "minimatch": "3.0.4", + "mockery": "^1.7.0", + "q": "^1.1.2", + "semver": "^5.1.0", + "shelljs": "^0.3.0", + "sync-request": "3.0.1", + "uuid": "^3.0.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "common": { + "version": "file:../common/msix-tasks-helpers-1.0.0.tgz", + "integrity": "sha512-hBVHDNpkQ6jDdAjg0jF2tnWgdqyg8R69eEchpCmiiiHVoUekFKQeYkmW+/CPQQ88kLU02CaPmV7XhDvYLUJ+0g==", + "requires": { + "@types/xml2js": "^0.4.8", + "adm-zip": "^0.5.5", + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "xml2js": "^0.4.23" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "http-basic": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-2.5.1.tgz", + "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.6", + "http-response-object": "^1.0.0" + } + }, + "http-response-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", + "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mockery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", + "integrity": "sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8=" + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "sync-request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-3.0.1.tgz", + "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", + "requires": { + "concat-stream": "^1.4.7", + "http-response-object": "^1.0.1", + "then-request": "^2.0.1" + } + }, + "then-request": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", + "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.7", + "http-basic": "^2.5.1", + "http-response-object": "^1.1.0", + "promise": "^7.1.1", + "qs": "^6.1.0" + } + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "typed-rest-client": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", + "integrity": "sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg==", + "requires": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "underscore": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.0.tgz", + "integrity": "sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + } + } +} diff --git a/tools/pipelines-tasks/AppInstallerFile/package.json b/tools/pipelines-tasks/AppInstallerFile/package.json new file mode 100644 index 000000000..23ebacc80 --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/package.json @@ -0,0 +1,20 @@ +{ + "name": "msix-tasks-app-installer-file", + "version": "1.0.0", + "description": "Task to create or update an App Installer file", + "repository": { + "type": "git", + "url": "git+github.com/microsoft/msix-ado-tasks-extension.git" + }, + "scripts": {}, + "author": "Microsoft Coporation", + "license": "MIT", + "dependencies": { + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "common": "file:../common/msix-tasks-helpers-1.0.0.tgz" + }, + "devDependencies": { + "@types/node": "^14.14.39" + } +} diff --git a/tools/pipelines-tasks/AppInstallerFile/task.json b/tools/pipelines-tasks/AppInstallerFile/task.json new file mode 100644 index 000000000..ddba605ac --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/task.json @@ -0,0 +1,461 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "ced3bc79-ea98-4c51-8a83-ed4e3a0a77ed", + "name": "AppInstallerFile", + "friendlyName": "App Installer file for MSIX", + "instanceNameFormat": "Create App Installer file", + "description": "Create or update an App Installer file for MSIX apps", + "author": "Microsoft Corporation", + "category": "Package", + "helpUrl": "https://aka.ms/msix-cicd", + "helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "execution": { + "Node10": { + "target": "index.js" + } + }, + "version": { + "Major": 1, + "Minor": 0, + "Patch": 0 + }, + "minimumAgentVersion": "1.95.0", + "inputs": [ + { + "name": "package", + "type": "string", + "label": "Package", + "required": true, + "helpMarkDown": "Path to the package or bundle." + }, + { + "name": "outputPath", + "type": "string", + "label": "Output File Path", + "defaultValue": "", + "required": true, + "helpMarkDown": "The path of the App Installer to be written." + }, + { + "name": "method", + "type": "pickList", + "label": "Method to Create App Installer File", + "defaultValue": "create", + "required": true, + "helpMarkDown": "Method used to create the App Installer file.", + "options": { + "create": "Create a new App Installer file", + "update": "Update an existing App Installer file" + } + }, + { + "name": "existingFile", + "type": "filePath", + "label": "Path to Existing App Installer File", + "required": true, + "helpMarkDown": "The path of the existing App Installer file to update.", + "visibleRule": "method = update" + }, + { + "name": "versionUpdateMethod", + "type": "pickList", + "label": "Method to Update the App Installer File's Version", + "required": true, + "defaultValue": "revision", + "helpMarkDown": "Increment the major/minor/build/revision of the App Installer file or enter a version number to be used.", + "options": { + "manual": "Manual Version", + "major": "Incremenet Major", + "minor": "Increment Minor", + "build": "Increment Build", + "revision": "Increment Revision" + }, + "visibleRule": "method = update" + }, + { + "name": "fileVersion", + "type": "string", + "label": "Version for App Installer file", + "required": true, + "defaultValue": "1.0.0.0", + "helpMarkDown": "The version number which will be given. Must take the form (major).(minor).(build).(revision).", + "visibleRule": "method = create || versionUpdateMethod = manual" + }, + { + "name": "uri", + "type": "string", + "label": "URI", + "required": true, + "helpMarkDown": "Web URI to the redirected App Installer file.", + "visibleRule": "method = create" + }, + { + "name": "mainItemUri", + "type": "string", + "label": "Main Package/Bundle URI", + "required": true, + "helpMarkDown": "URI to the app package/bundle location.", + "visibleRule": "method = create" + }, + { + "name": "updateOnLaunch", + "type": "boolean", + "label": "Update On Launch", + "defaultValue": true, + "required": false, + "helpMarkDown": "Set the app to check for updates when launched.", + "visibleRule": "method = create" + }, + { + "name": "hoursBetweenUpdateChecks", + "type": "string", + "label": "Hours Between Update Checks", + "defaultValue": "24", + "required": false, + "helpMarkDown": "How often the system will check for updates to the app.", + "visibleRule": "method = create && updateOnLaunch = true" + }, + { + "name": "showPromptWhenUpdating", + "type": "boolean", + "label": "Show UI to User when Updating", + "defaultValue": false, + "required": false, + "helpMarkDown": "Show a UI to notify the user if an update occurs when launching the app.", + "visibleRule": "method = create && updateOnLaunch = true" + }, + { + "name": "updateBlocksActivation", + "type": "boolean", + "label": "Update Blocks App Activation", + "defaultValue": false, + "required": false, + "helpMarkDown": "Block the app from launching until the update finishes.", + "visibleRule": "method = create && updateOnLaunch = true && showPromptWhenUpdating = true" + }, + { + "name": "addOptionalItem1", + "type": "pickList", + "label": "Add an Optional Package/Bundle", + "defaultValue": "none", + "required": false, + "helpMarkDown": "Add an optional package or bundle", + "visibleRule": "method = create", + "options": { + "none": "No Optional Package/Bundle", + "package": "Add Optional Package", + "bundle": "Add Optional Bundle" + } + }, + { + "name": "optionalItem1Name", + "type": "string", + "label": "Optional Item 1: Name", + "required": true, + "helpMarkDown": "The package or bundle name of your first optional item to include.", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "optionalItem1Publisher", + "type": "string", + "label": "Optional Item 1: Publisher", + "required": true, + "helpMarkDown": "The publisher name of the first optional item to include.", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "optionalItem1Version", + "type": "string", + "label": "Optional Item 1: Version", + "required": true, + "helpMarkDown": "The version number of the first optional item to include.", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "optionalItem1ProcessorArchitecture", + "type": "string", + "label": "Optional Item 1: Processor Architecture", + "required": true, + "helpMarkDown": "The processor architecture of the first optional item to include.", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "optionalItem1URI", + "type": "string", + "label": "Optional Item 1: URI", + "required": true, + "helpMarkDown": "The URI of the first optional item to include.", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "addOptionalItem2", + "type": "pickList", + "label": "Add a Second Optional Package/Bundle", + "defaultValue": "none", + "required": false, + "helpMarkDown": "Add a second optional package or bundle", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle", + "options": { + "none": "No Optional Package/Bundle", + "package": "Add Optional Package", + "bundle": "Add Optional Bundle" + } + }, + { + "name": "optionalItem2Name", + "type": "string", + "label": "Optional Item 2: Name", + "required": true, + "helpMarkDown": "The package or bundle name of your second optional item to include.", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "optionalItem2Publisher", + "type": "string", + "label": "Optional Item 2: Publisher", + "required": true, + "helpMarkDown": "The publisher name of the second optional item to include.", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "optionalItem2Version", + "type": "string", + "label": "Optional Item 2: Version", + "required": true, + "helpMarkDown": "The version number of the second optional item to include.", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "optionalItem2ProcessorArchitecture", + "type": "string", + "label": "Optional Item 2: Processor Architecture", + "required": true, + "helpMarkDown": "The processor architecture of the second optional item to include.", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "optionalItem2URI", + "type": "string", + "label": "Optional Item 2: URI", + "required": true, + "helpMarkDown": "The URI of the second optional item to include.", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "addOptionalItem3", + "type": "pickList", + "label": "Add a Third Optional Package/Bundle", + "defaultValue": "none", + "required": false, + "helpMarkDown": "Add a third optional package or bundle", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle", + "options": { + "none": "No Optional Package/Bundle", + "package": "Add Optional Package", + "bundle": "Add Optional Bundle" + } + }, + { + "name": "optionalItem3Name", + "type": "string", + "label": "Optional Item 3: Name", + "required": true, + "helpMarkDown": "The package or bundle name of your third optional item to include.", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "optionalItem3Publisher", + "type": "string", + "label": "Optional Item 3: Publisher", + "required": true, + "helpMarkDown": "The publisher name of the third optional item to include.", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "optionalItem3Version", + "type": "string", + "label": "Optional Item 3: Version", + "required": true, + "helpMarkDown": "The version number of the third optional item to include.", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "optionalItem3ProcessorArchitecture", + "type": "string", + "label": "Optional Item 3: Processor Architecture", + "required": true, + "helpMarkDown": "The processor architecture of the third optional item to include.", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "optionalItem3URI", + "type": "string", + "label": "Optional Item 3: URI", + "required": true, + "helpMarkDown": "The URI of the third optional item to include.", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "addDependency1", + "type": "pickList", + "label": "Add a Dependency", + "defaultValue": "none", + "required": false, + "helpMarkDown": "Add a dependency.", + "visibleRule": "method = create", + "options": { + "none": "No Dependency", + "package": "Add Package Dependency", + "bundle": "Add Bundle Dependency" + } + }, + { + "name": "dependency1Name", + "type": "string", + "label": "Dependency 1: Name", + "required": true, + "helpMarkDown": "The name of the first dependency to include.", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "dependency1Publisher", + "type": "string", + "label": "Dependency 1: Publisher", + "required": true, + "helpMarkDown": "The publisher name of the first dependency to include.", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "dependency1Version", + "type": "string", + "label": "Dependency 1: Version", + "required": true, + "helpMarkDown": "The version number of the first dependency to include.", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "dependency1ProcessorArchitecture", + "type": "string", + "label": "Dependency 1: Processor Architecture", + "required": true, + "helpMarkDown": "The processor architecture of the first dependency to include.", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "dependency1URI", + "type": "string", + "label": "Dependency 1: URI", + "required": true, + "helpMarkDown": "The URI of the first dependency to include.", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "addDependency2", + "type": "pickList", + "label": "Add a Second Dependency", + "defaultValue": "none", + "required": false, + "helpMarkDown": "Add a second dependency.", + "visibleRule": "addDependency1 == package || addDependency1 == bundle", + "options": { + "none": "No Dependency", + "package": "Add Package Dependency", + "bundle": "Add Bundle Dependency" + } + }, + { + "name": "dependency2Name", + "type": "string", + "label": "Dependency 2: Name", + "required": true, + "helpMarkDown": "The name of the second dependency to include.", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "dependency2Publisher", + "type": "string", + "label": "Dependency 2: Publisher", + "required": true, + "helpMarkDown": "The publisher name of the second dependency to include.", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "dependency2Version", + "type": "string", + "label": "Dependency 2: Version", + "required": true, + "helpMarkDown": "The version number of the second dependency to include.", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "dependency2ProcessorArchitecture", + "type": "string", + "label": "Dependency 2: Processor Architecture", + "required": true, + "helpMarkDown": "The processor architecture of the second dependency to include.", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "dependency2URI", + "type": "string", + "label": "Dependency 2: URI", + "required": true, + "helpMarkDown": "The URI of the second dependency to include.", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "addDependency3", + "type": "pickList", + "label": "Add a Third Dependency", + "defaultValue": "none", + "required": false, + "helpMarkDown": "Add a third dependency.", + "visibleRule": "addDependency2 == package || addDependency2 == bundle", + "options": { + "none": "No Dependency", + "package": "Add Package Dependency", + "bundle": "Add Bundle Dependency" + } + }, + { + "name": "dependency3Name", + "type": "string", + "label": "Dependency 3: Name", + "required": true, + "helpMarkDown": "The name of the third dependency to include.", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + }, + { + "name": "dependency3Publisher", + "type": "string", + "label": "Dependency 3: Publisher", + "required": true, + "helpMarkDown": "The publisher name of the third dependency to include.", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + }, + { + "name": "dependency3Version", + "type": "string", + "label": "Dependency 3: Version", + "required": true, + "helpMarkDown": "The version number of the third dependency to include.", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + }, + { + "name": "dependency3ProcessorArchitecture", + "type": "string", + "label": "Dependency 3: Processor Architecture", + "required": true, + "helpMarkDown": "The processor architecture of the third dependency to include.", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + }, + { + "name": "dependency3URI", + "type": "string", + "label": "Dependency 3: URI", + "required": true, + "helpMarkDown": "The URI of the third dependency to include.", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + } + ] +} diff --git a/tools/pipelines-tasks/AppInstallerFile/task.loc.json b/tools/pipelines-tasks/AppInstallerFile/task.loc.json new file mode 100644 index 000000000..a3eca832c --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/task.loc.json @@ -0,0 +1,461 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "ced3bc79-ea98-4c51-8a83-ed4e3a0a77ed", + "name": "AppInstallerFile", + "friendlyName": "ms-resource:loc.friendlyName", + "instanceNameFormat": "ms-resource:loc.instanceNameFormat", + "description": "ms-resource:loc.description", + "author": "Microsoft Corporation", + "category": "Package", + "helpUrl": "https://aka.ms/msix-cicd", + "helpMarkDown": "ms-resource:loc.helpMarkDown", + "execution": { + "Node10": { + "target": "index.js" + } + }, + "version": { + "Major": 1, + "Minor": 0, + "Patch": 0 + }, + "minimumAgentVersion": "1.95.0", + "inputs": [ + { + "name": "package", + "type": "string", + "label": "ms-resource:loc.input.label.package", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.package" + }, + { + "name": "outputPath", + "type": "string", + "label": "ms-resource:loc.input.label.outputPath", + "defaultValue": "", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.outputPath" + }, + { + "name": "method", + "type": "pickList", + "label": "ms-resource:loc.input.label.method", + "defaultValue": "create", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.method", + "options": { + "create": "Create a new App Installer file", + "update": "Update an existing App Installer file" + } + }, + { + "name": "existingFile", + "type": "filePath", + "label": "ms-resource:loc.input.label.existingFile", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.existingFile", + "visibleRule": "method = update" + }, + { + "name": "versionUpdateMethod", + "type": "pickList", + "label": "ms-resource:loc.input.label.versionUpdateMethod", + "required": true, + "defaultValue": "revision", + "helpMarkDown": "ms-resource:loc.input.help.versionUpdateMethod", + "options": { + "manual": "Manual Version", + "major": "Incremenet Major", + "minor": "Increment Minor", + "build": "Increment Build", + "revision": "Increment Revision" + }, + "visibleRule": "method = update" + }, + { + "name": "fileVersion", + "type": "string", + "label": "ms-resource:loc.input.label.fileVersion", + "required": true, + "defaultValue": "1.0.0.0", + "helpMarkDown": "ms-resource:loc.input.help.fileVersion", + "visibleRule": "method = create || versionUpdateMethod = manual" + }, + { + "name": "uri", + "type": "string", + "label": "ms-resource:loc.input.label.uri", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.uri", + "visibleRule": "method = create" + }, + { + "name": "mainItemUri", + "type": "string", + "label": "ms-resource:loc.input.label.mainItemUri", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.mainItemUri", + "visibleRule": "method = create" + }, + { + "name": "updateOnLaunch", + "type": "boolean", + "label": "ms-resource:loc.input.label.updateOnLaunch", + "defaultValue": true, + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.updateOnLaunch", + "visibleRule": "method = create" + }, + { + "name": "hoursBetweenUpdateChecks", + "type": "string", + "label": "ms-resource:loc.input.label.hoursBetweenUpdateChecks", + "defaultValue": "24", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.hoursBetweenUpdateChecks", + "visibleRule": "method = create && updateOnLaunch = true" + }, + { + "name": "showPromptWhenUpdating", + "type": "boolean", + "label": "ms-resource:loc.input.label.showPromptWhenUpdating", + "defaultValue": false, + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.showPromptWhenUpdating", + "visibleRule": "method = create && updateOnLaunch = true" + }, + { + "name": "updateBlocksActivation", + "type": "boolean", + "label": "ms-resource:loc.input.label.updateBlocksActivation", + "defaultValue": false, + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.updateBlocksActivation", + "visibleRule": "method = create && updateOnLaunch = true && showPromptWhenUpdating = true" + }, + { + "name": "addOptionalItem1", + "type": "pickList", + "label": "ms-resource:loc.input.label.addOptionalItem1", + "defaultValue": "none", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.addOptionalItem1", + "visibleRule": "method = create", + "options": { + "none": "No Optional Package/Bundle", + "package": "Add Optional Package", + "bundle": "Add Optional Bundle" + } + }, + { + "name": "optionalItem1Name", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem1Name", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem1Name", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "optionalItem1Publisher", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem1Publisher", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem1Publisher", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "optionalItem1Version", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem1Version", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem1Version", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "optionalItem1ProcessorArchitecture", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem1ProcessorArchitecture", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem1ProcessorArchitecture", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "optionalItem1URI", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem1URI", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem1URI", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle" + }, + { + "name": "addOptionalItem2", + "type": "pickList", + "label": "ms-resource:loc.input.label.addOptionalItem2", + "defaultValue": "none", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.addOptionalItem2", + "visibleRule": "addOptionalItem1 == package || addOptionalItem1 == bundle", + "options": { + "none": "No Optional Package/Bundle", + "package": "Add Optional Package", + "bundle": "Add Optional Bundle" + } + }, + { + "name": "optionalItem2Name", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem2Name", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem2Name", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "optionalItem2Publisher", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem2Publisher", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem2Publisher", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "optionalItem2Version", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem2Version", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem2Version", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "optionalItem2ProcessorArchitecture", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem2ProcessorArchitecture", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem2ProcessorArchitecture", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "optionalItem2URI", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem2URI", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem2URI", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle" + }, + { + "name": "addOptionalItem3", + "type": "pickList", + "label": "ms-resource:loc.input.label.addOptionalItem3", + "defaultValue": "none", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.addOptionalItem3", + "visibleRule": "addOptionalItem2 == package || addOptionalItem2 == bundle", + "options": { + "none": "No Optional Package/Bundle", + "package": "Add Optional Package", + "bundle": "Add Optional Bundle" + } + }, + { + "name": "optionalItem3Name", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem3Name", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem3Name", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "optionalItem3Publisher", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem3Publisher", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem3Publisher", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "optionalItem3Version", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem3Version", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem3Version", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "optionalItem3ProcessorArchitecture", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem3ProcessorArchitecture", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem3ProcessorArchitecture", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "optionalItem3URI", + "type": "string", + "label": "ms-resource:loc.input.label.optionalItem3URI", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.optionalItem3URI", + "visibleRule": "addOptionalItem3 == package || addOptionalItem3 == bundle" + }, + { + "name": "addDependency1", + "type": "pickList", + "label": "ms-resource:loc.input.label.addDependency1", + "defaultValue": "none", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.addDependency1", + "visibleRule": "method = create", + "options": { + "none": "No Dependency", + "package": "Add Package Dependency", + "bundle": "Add Bundle Dependency" + } + }, + { + "name": "dependency1Name", + "type": "string", + "label": "ms-resource:loc.input.label.dependency1Name", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency1Name", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "dependency1Publisher", + "type": "string", + "label": "ms-resource:loc.input.label.dependency1Publisher", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency1Publisher", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "dependency1Version", + "type": "string", + "label": "ms-resource:loc.input.label.dependency1Version", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency1Version", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "dependency1ProcessorArchitecture", + "type": "string", + "label": "ms-resource:loc.input.label.dependency1ProcessorArchitecture", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency1ProcessorArchitecture", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "dependency1URI", + "type": "string", + "label": "ms-resource:loc.input.label.dependency1URI", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency1URI", + "visibleRule": "addDependency1 == package || addDependency1 == bundle" + }, + { + "name": "addDependency2", + "type": "pickList", + "label": "ms-resource:loc.input.label.addDependency2", + "defaultValue": "none", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.addDependency2", + "visibleRule": "addDependency1 == package || addDependency1 == bundle", + "options": { + "none": "No Dependency", + "package": "Add Package Dependency", + "bundle": "Add Bundle Dependency" + } + }, + { + "name": "dependency2Name", + "type": "string", + "label": "ms-resource:loc.input.label.dependency2Name", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency2Name", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "dependency2Publisher", + "type": "string", + "label": "ms-resource:loc.input.label.dependency2Publisher", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency2Publisher", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "dependency2Version", + "type": "string", + "label": "ms-resource:loc.input.label.dependency2Version", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency2Version", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "dependency2ProcessorArchitecture", + "type": "string", + "label": "ms-resource:loc.input.label.dependency2ProcessorArchitecture", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency2ProcessorArchitecture", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "dependency2URI", + "type": "string", + "label": "ms-resource:loc.input.label.dependency2URI", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency2URI", + "visibleRule": "addDependency2 == package || addDependency2 == bundle" + }, + { + "name": "addDependency3", + "type": "pickList", + "label": "ms-resource:loc.input.label.addDependency3", + "defaultValue": "none", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.addDependency3", + "visibleRule": "addDependency2 == package || addDependency2 == bundle", + "options": { + "none": "No Dependency", + "package": "Add Package Dependency", + "bundle": "Add Bundle Dependency" + } + }, + { + "name": "dependency3Name", + "type": "string", + "label": "ms-resource:loc.input.label.dependency3Name", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency3Name", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + }, + { + "name": "dependency3Publisher", + "type": "string", + "label": "ms-resource:loc.input.label.dependency3Publisher", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency3Publisher", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + }, + { + "name": "dependency3Version", + "type": "string", + "label": "ms-resource:loc.input.label.dependency3Version", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency3Version", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + }, + { + "name": "dependency3ProcessorArchitecture", + "type": "string", + "label": "ms-resource:loc.input.label.dependency3ProcessorArchitecture", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency3ProcessorArchitecture", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + }, + { + "name": "dependency3URI", + "type": "string", + "label": "ms-resource:loc.input.label.dependency3URI", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.dependency3URI", + "visibleRule": "addDependency3 == package || addDependency3 == bundle" + } + ] +} \ No newline at end of file diff --git a/tools/pipelines-tasks/AppInstallerFile/tsconfig.json b/tools/pipelines-tasks/AppInstallerFile/tsconfig.json new file mode 100644 index 000000000..3c43903cf --- /dev/null +++ b/tools/pipelines-tasks/AppInstallerFile/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json" +} diff --git a/tools/pipelines-tasks/MsixAppAttach/GenerateAppAttachVhdx.ps1 b/tools/pipelines-tasks/MsixAppAttach/GenerateAppAttachVhdx.ps1 new file mode 100644 index 000000000..0c01f14d7 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/GenerateAppAttachVhdx.ps1 @@ -0,0 +1,72 @@ +# Note: This script needs to be run with admin privileges. + +param +( + # Must be a full path + [Parameter(Mandatory=$true)] + [string]$vhdxPath, + + # Size in MB. + [Parameter(Mandatory=$true)] + [int]$vhdxSize, + + [Parameter(Mandatory=$true)] + [string]$msixPackagePath, + + [Parameter(Mandatory=$true)] + [string]$msixmgrPath +) + +$ErrorActionPreference = "Stop" +$PSDefaultParameterValues['*:Encoding'] = 'utf8' + +Remove-Item $vhdxPath -ErrorAction SilentlyContinue + +# Create parent directory +New-Item -ItemType Directory -Path (Split-Path $vhdxPath) -ErrorAction SilentlyContinue + +# Create the disk +# Note that the docs for creating a VHDX for app attach say to use +# New-VHD -SizeBytes $vhdxSize -Path $vhdxPath -Dynamic -Confirm:$false +# but New-VHD is not available on the build agents. +# Instead we use diskpart. +# diskpart is an interactive program. We pass it a script of what we want to do. +# We could also pass it through the standard input. +$scriptFile = Join-Path $env:TEMP 'create-vhdx.txt' +$diskpartCommand = "create vdisk file=`"$vhdxPath`" maximum=$vhdxSize" +Write-Host "diskpart command: $diskpartCommand" +$diskpartCommand | Out-File $scriptFile + +try +{ + Start-Process -Wait diskpart /s,$scriptFile + if (-not $?) + { + throw "Diskpart failed to create new virtual disk." + } + + # Mount the disk + $vhdxObject = Mount-DiskImage $vhdxPath -Passthru + if (-not $?) + { + throw "Mounting virtual disk failed." + } + + # Format the disk + $vhdxObject | Format-List + $disk = Initialize-Disk -Passthru -Number $vhdxObject.Number -PartitionStyle MBR + $partition = New-Partition -AssignDriveLetter -UseMaximumSize -DiskNumber $disk.Number + Format-Volume -FileSystem NTFS -Confirm:$false -DriveLetter $partition.DriveLetter -Force + + # Create a directory to hold the app + $vhdxRoot = $partition.DriveLetter + ':\VHDXRoot' + New-Item -Path $vhdxRoot -ItemType Directory + + # Unpack the app to the app into the distk + & $msixmgrPath -Unpack -packagePath $msixPackagePath -destination $vhdxRoot -applyacls +} +finally +{ + # Remove-Item $scriptFile + Dismount-DiskImage $vhdxPath -ErrorAction SilentlyContinue +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/de-DE/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/de-DE/resources.resjson new file mode 100644 index 000000000..4b1516a0a --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/de-DE/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "Paket für MSIX App Attach erstellen", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Ein VHD- oder VHDX-Paket für MSIX App Attach erstellen", + "loc.instanceNameFormat": "Paket für MSIX App Attach erstellen", + "loc.input.label.package": "Paketpfad", + "loc.input.help.package": "Pfad zum Paket.", + "loc.input.label.vhdxOutputPath": "VHDX-Ausgabepfad", + "loc.input.help.vhdxOutputPath": "Der Name der VHDX-Datei, die von der Aufgabe erstellt wird.", + "loc.input.label.vhdxSize": "VHDX-Größe (in MB)", + "loc.input.help.vhdxSize": "Die maximale Größe des VHDX in MBs." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/en-US/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/en-US/resources.resjson new file mode 100644 index 000000000..ca4c283a9 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/en-US/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "Create package for MSIX app attach", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Create a VHD or VHDX package for MSIX app attach", + "loc.instanceNameFormat": "Create package for MSIX app attach", + "loc.input.label.package": "Package Path", + "loc.input.help.package": "Path to the package.", + "loc.input.label.vhdxOutputPath": "VHDX Output Path", + "loc.input.help.vhdxOutputPath": "The name of the VHDX file that will be created by the task.", + "loc.input.label.vhdxSize": "VHDX size (in MB)", + "loc.input.help.vhdxSize": "The maximum size in MBs of the VHDX." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/es-ES/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/es-ES/resources.resjson new file mode 100644 index 000000000..2645c30cb --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/es-ES/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "Crear un paquete para la conexión de aplicaciones MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Crear un paquete VHD o VHDX para adjuntar la aplicación de MSIX", + "loc.instanceNameFormat": "Crear un paquete para la conexión de aplicaciones MSIX", + "loc.input.label.package": "Ruta de acceso del paquete:", + "loc.input.help.package": "La ruta de acceso del paquete.", + "loc.input.label.vhdxOutputPath": "Ruta de acceso de salida VHDX", + "loc.input.help.vhdxOutputPath": "El nombre del archivo VHDX que se creará en la tarea.", + "loc.input.label.vhdxSize": "Tamaño de VHDX (en MB)", + "loc.input.help.vhdxSize": "El tamaño máximo en MB del VHDX." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/fr-FR/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/fr-FR/resources.resjson new file mode 100644 index 000000000..ef147f21e --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/fr-FR/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "Créer un package pour l’attachement de l’application MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Créer un package VHD ou VHDX pour une connexion d’application MSIX", + "loc.instanceNameFormat": "Créer un package pour l’attachement de l’application MSIX", + "loc.input.label.package": "Chemin d'accès du package", + "loc.input.help.package": "Chemin d’accès du package.", + "loc.input.label.vhdxOutputPath": "Chemin d’accès de sortie de VHDX", + "loc.input.help.vhdxOutputPath": "Nom du fichier VHDX qui sera créé par la tâche.", + "loc.input.label.vhdxSize": "Taille de VHDX (en Mo)", + "loc.input.help.vhdxSize": "Taille maximale en mégaoctets du VHDX." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/it-IT/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/it-IT/resources.resjson new file mode 100644 index 000000000..1ab9d570c --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/it-IT/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "Crea pacchetto per MSIX App Attach", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Creare un pacchetto VHD o VHDX per l'app MSIX", + "loc.instanceNameFormat": "Crea pacchetto per MSIX App Attach", + "loc.input.label.package": "Percorso pacchetto", + "loc.input.help.package": "Percorso del pacchetto.", + "loc.input.label.vhdxOutputPath": "Percorso di output VHDX", + "loc.input.help.vhdxOutputPath": "Il nome del file VHDX che sarà creato dall'attività.", + "loc.input.label.vhdxSize": "Dimensione VHDX (in MB)", + "loc.input.help.vhdxSize": "Dimensioni massime in MB del VHDX." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/ja-JP/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/ja-JP/resources.resjson new file mode 100644 index 000000000..6b2b9c449 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/ja-JP/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "MSIX アプリ アタッチのパッケージを作成する", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "MSIX アプリ アタッチの VHD パッケージまたは VHDX パッケージを作成する", + "loc.instanceNameFormat": "MSIX アプリ アタッチのパッケージを作成する", + "loc.input.label.package": "パッケージのパス", + "loc.input.help.package": "パッケージへのパスです。", + "loc.input.label.vhdxOutputPath": "VHDX 出力パス", + "loc.input.help.vhdxOutputPath": "タスクによって作成される VHDX ファイルの名前です。", + "loc.input.label.vhdxSize": "VHDX のサイズ (MB 単位)", + "loc.input.help.vhdxSize": "VHDX の最大サイズ (MB 単位) です。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/ko-KR/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/ko-KR/resources.resjson new file mode 100644 index 000000000..1b743492d --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/ko-KR/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "MSIX 앱 연결용 패키지 만들기", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "MSIX 앱 연결을 위한 VHD 또는 VHDX 패키지 만들기", + "loc.instanceNameFormat": "MSIX 앱 연결용 패키지 만들기", + "loc.input.label.package": "패키지 경로", + "loc.input.help.package": "패키지의 경로입니다.", + "loc.input.label.vhdxOutputPath": "VHDX 출력 경로", + "loc.input.help.vhdxOutputPath": "작업에서 만드는 VHDX 파일의 이름입니다.", + "loc.input.label.vhdxSize": "VHDX 크기(MB)", + "loc.input.help.vhdxSize": "VHDX의 최대 크기(Mb)입니다." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/pt-BR/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/pt-BR/resources.resjson new file mode 100644 index 000000000..b49a7ece7 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/pt-BR/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "Criar pacote para anexação de aplicativo MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Criar um pacote VHD ou VHDX para anexar MSIX app", + "loc.instanceNameFormat": "Criar pacote para anexação de aplicativo MSIX", + "loc.input.label.package": "Caminho do Pacote", + "loc.input.help.package": "Caminho ao pacote.", + "loc.input.label.vhdxOutputPath": "Caminho de Saída VHDX", + "loc.input.help.vhdxOutputPath": "O nome do arquivo de VHDX que será criado pela tarefa.", + "loc.input.label.vhdxSize": "Tamanho VHDX (em MB)", + "loc.input.help.vhdxSize": "O tamanho máximo em MBs do VHDX." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/ru-RU/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/ru-RU/resources.resjson new file mode 100644 index 000000000..7fe577e21 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/ru-RU/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "Создание пакета для подключения приложений MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Создать VHD-или VHDX-пакет для MSIX-приложения", + "loc.instanceNameFormat": "Создание пакета для подключения приложений MSIX", + "loc.input.label.package": "Путь к пакету", + "loc.input.help.package": "Путь к пакету.", + "loc.input.label.vhdxOutputPath": "Путь вывода VHDX", + "loc.input.help.vhdxOutputPath": "Имя VHDX-файла, который будет создан задачей.", + "loc.input.label.vhdxSize": "Размер VHDX (в Мб)", + "loc.input.help.vhdxSize": "Максимальный размер VHDX-файла (в МБ)." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/zh-CN/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/zh-CN/resources.resjson new file mode 100644 index 000000000..8e0076719 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/zh-CN/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "创建 MSIX 应用附加程序包", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "为 MSIX 应用附加创建 VHD 或 VHDX 程序包", + "loc.instanceNameFormat": "创建 MSIX 应用附加程序包", + "loc.input.label.package": "程序包路径", + "loc.input.help.package": "程序包的路径。", + "loc.input.label.vhdxOutputPath": "VHDX 输出路径", + "loc.input.help.vhdxOutputPath": "将由任务创建的 VHDX 文件的名称。", + "loc.input.label.vhdxSize": "VHDX 大小 (MB)", + "loc.input.help.vhdxSize": "VHDX的最大大小(以 MB 为单位)。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/zh-TW/resources.resjson b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/zh-TW/resources.resjson new file mode 100644 index 000000000..322edf566 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/Strings/resources.resjson/zh-TW/resources.resjson @@ -0,0 +1,12 @@ +{ + "loc.friendlyName": "建立 MSIX 應用程式連結套件", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "建立 MSIX 應用程式附加的 VHD 或 VHDX 套件", + "loc.instanceNameFormat": "建立 MSIX 應用程式連結套件", + "loc.input.label.package": "套件路徑", + "loc.input.help.package": "套件的路徑。", + "loc.input.label.vhdxOutputPath": "VHDX 輸出路徑", + "loc.input.help.vhdxOutputPath": "該工作將建立的 VHDX 檔案名稱。", + "loc.input.label.vhdxSize": "VHDX 大小 (MB)", + "loc.input.help.vhdxSize": "VHDX 的大小上限 (MB)。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/debug_inputs.env b/tools/pipelines-tasks/MsixAppAttach/debug_inputs.env new file mode 100644 index 000000000..2c9035a7c --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/debug_inputs.env @@ -0,0 +1,5 @@ +# Set the task's inputs here to use when debugging + +INPUT_PACKAGE=test\assets\existingPackage.msix +INPUT_VHDXOUTPUTPATH=debug\app.vhdx +INPUT_VHDXSIZE=100 \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/icon.png b/tools/pipelines-tasks/MsixAppAttach/icon.png new file mode 100644 index 000000000..052291f00 Binary files /dev/null and b/tools/pipelines-tasks/MsixAppAttach/icon.png differ diff --git a/tools/pipelines-tasks/MsixAppAttach/index.ts b/tools/pipelines-tasks/MsixAppAttach/index.ts new file mode 100644 index 000000000..6a34e2ddb --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/index.ts @@ -0,0 +1,38 @@ +import path = require('path'); +import tl = require('azure-pipelines-task-lib/task') + +import { ToolRunner } from 'azure-pipelines-task-lib/toolrunner'; + +import helpers = require('common/helpers'); + +const MSIXMGR_PATH = path.join(__dirname, 'lib', 'msixmgr'); +const GENERATE_VHDX_SCRIPT_PATH = path.join(__dirname, 'GenerateAppAttachVhdx.ps1') + +/** + * Main function for the task. + */ +const run = async () => +{ + tl.setResourcePath(path.join(__dirname, 'task.json')); + + // Read the task's inputs + const packagePath: string = helpers.getInputWithErrorCheck('package', 'No package path specified.'); + const vhdxPath: string = helpers.getInputWithErrorCheck('vhdxOutputPath', 'A path is needed to create a new VHDX file, but none was given.'); + const vhdxSize: string = helpers.getInputWithErrorCheck('vhdxSize', 'A size is needed to create a new VHDX file, but none was given.'); + + // The script requires the command path to be absolute. + const fullVhdxPath: string = path.resolve(vhdxPath); + const powershellRunner: ToolRunner = helpers.getPowershellRunner(GENERATE_VHDX_SCRIPT_PATH); + powershellRunner.arg(['-vhdxPath', fullVhdxPath]); + powershellRunner.arg(['-vhdxSize', vhdxSize]); + powershellRunner.arg(['-msixPackagePath', packagePath]); + powershellRunner.arg(['-msixmgrPath', MSIXMGR_PATH]); + + // This script needs to be run as administrator. + await powershellRunner.exec(); +} + +run().catch(err => + { + tl.setResult(tl.TaskResult.Failed, err.message); + }) \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/lib/applyacls.dll b/tools/pipelines-tasks/MsixAppAttach/lib/applyacls.dll new file mode 100644 index 000000000..6f9de8254 Binary files /dev/null and b/tools/pipelines-tasks/MsixAppAttach/lib/applyacls.dll differ diff --git a/tools/pipelines-tasks/MsixAppAttach/lib/en-US/msixmgr.exe.mui b/tools/pipelines-tasks/MsixAppAttach/lib/en-US/msixmgr.exe.mui new file mode 100644 index 000000000..89f820a18 Binary files /dev/null and b/tools/pipelines-tasks/MsixAppAttach/lib/en-US/msixmgr.exe.mui differ diff --git a/tools/pipelines-tasks/MsixAppAttach/lib/msix.dll b/tools/pipelines-tasks/MsixAppAttach/lib/msix.dll new file mode 100644 index 000000000..8c1245c09 Binary files /dev/null and b/tools/pipelines-tasks/MsixAppAttach/lib/msix.dll differ diff --git a/tools/pipelines-tasks/MsixAppAttach/lib/msixmgr.exe b/tools/pipelines-tasks/MsixAppAttach/lib/msixmgr.exe new file mode 100644 index 000000000..fcfacff94 Binary files /dev/null and b/tools/pipelines-tasks/MsixAppAttach/lib/msixmgr.exe differ diff --git a/tools/pipelines-tasks/MsixAppAttach/package-lock.json b/tools/pipelines-tasks/MsixAppAttach/package-lock.json new file mode 100644 index 000000000..d03d154d7 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/package-lock.json @@ -0,0 +1,340 @@ +{ + "name": "msix-tasks-app-attach", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "14.14.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz", + "integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw==" + }, + "@types/xml2js": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.8.tgz", + "integrity": "sha512-EyvT83ezOdec7BhDaEcsklWy7RSIdi6CNe95tmOAK0yx/Lm30C9K75snT3fYayK59ApC2oyW+rcHErdG05FHJA==", + "requires": { + "@types/node": "*" + } + }, + "adm-zip": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "azure-devops-node-api": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-10.2.2.tgz", + "integrity": "sha512-4TVv2X7oNStT0vLaEfExmy3J4/CzfuXolEcQl/BRUmvGySqKStTG2O55/hUQ0kM7UJlZBLgniM0SBq4d/WkKow==", + "requires": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "azure-pipelines-task-lib": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.12.2.tgz", + "integrity": "sha512-ofAdVZcL90Qv6zYcKa1vK3Wnrl2kxoKX/Idvb7RWrqHQzcJlAEjCU4UCB5y6NnSKqRSyVTIhdS6hChphpOaiMQ==", + "requires": { + "minimatch": "3.0.4", + "mockery": "^1.7.0", + "q": "^1.1.2", + "semver": "^5.1.0", + "shelljs": "^0.3.0", + "sync-request": "3.0.1", + "uuid": "^3.0.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "common": { + "version": "file:../common/msix-tasks-helpers-1.0.0.tgz", + "integrity": "sha512-hBVHDNpkQ6jDdAjg0jF2tnWgdqyg8R69eEchpCmiiiHVoUekFKQeYkmW+/CPQQ88kLU02CaPmV7XhDvYLUJ+0g==", + "requires": { + "@types/xml2js": "^0.4.8", + "adm-zip": "^0.5.5", + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "xml2js": "^0.4.23" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "http-basic": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-2.5.1.tgz", + "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.6", + "http-response-object": "^1.0.0" + } + }, + "http-response-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", + "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mockery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", + "integrity": "sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8=" + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "sync-request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-3.0.1.tgz", + "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", + "requires": { + "concat-stream": "^1.4.7", + "http-response-object": "^1.0.1", + "then-request": "^2.0.1" + } + }, + "then-request": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", + "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.7", + "http-basic": "^2.5.1", + "http-response-object": "^1.1.0", + "promise": "^7.1.1", + "qs": "^6.1.0" + } + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "typed-rest-client": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", + "integrity": "sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg==", + "requires": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "underscore": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.0.tgz", + "integrity": "sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + } + } +} diff --git a/tools/pipelines-tasks/MsixAppAttach/package.json b/tools/pipelines-tasks/MsixAppAttach/package.json new file mode 100644 index 000000000..db5470fff --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/package.json @@ -0,0 +1,20 @@ +{ + "name": "msix-tasks-app-attach", + "version": "1.0.0", + "description": "Task to create VHDX files for MSIX app attach", + "repository": { + "type": "git", + "url": "git+github.com/microsoft/msix-ado-tasks-extension.git" + }, + "scripts": {}, + "author": "Microsoft Coporation", + "license": "MIT", + "dependencies": { + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "common": "file:../common/msix-tasks-helpers-1.0.0.tgz" + }, + "devDependencies": { + "@types/node": "^14.14.39" + } +} diff --git a/tools/pipelines-tasks/MsixAppAttach/task.json b/tools/pipelines-tasks/MsixAppAttach/task.json new file mode 100644 index 000000000..33828c364 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/task.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "5acdf104-665e-4e06-a565-9f0a88e7de2b", + "name": "MsixAppAttach", + "friendlyName": "Create package for MSIX app attach", + "instanceNameFormat": "Create package for MSIX app attach", + "description": "Create a VHD or VHDX package for MSIX app attach", + "author": "Microsoft Corporation", + "category": "Package", + "helpUrl": "https://aka.ms/msix-cicd", + "helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "execution": { + "Node10": { + "target": "index.js", + "platforms": [ + "windows" + ] + } + }, + "version": { + "Major": 1, + "Minor": 0, + "Patch": 0 + }, + "minimumAgentVersion": "1.95.0", + "inputs": [ + { + "name": "package", + "type": "string", + "label": "Package Path", + "required": true, + "helpMarkDown": "Path to the package." + }, + { + "name": "vhdxOutputPath", + "type": "string", + "label": "VHDX Output Path", + "required": true, + "helpMarkDown": "The name of the VHDX file that will be created by the task." + }, + { + "name": "vhdxSize", + "type": "string", + "label": "VHDX size (in MB)", + "defaultValue": "100", + "required": true, + "helpMarkDown": "The maximum size in MBs of the VHDX." + } + ] +} diff --git a/tools/pipelines-tasks/MsixAppAttach/task.loc.json b/tools/pipelines-tasks/MsixAppAttach/task.loc.json new file mode 100644 index 000000000..814eb08b3 --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/task.loc.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "5acdf104-665e-4e06-a565-9f0a88e7de2b", + "name": "MsixAppAttach", + "friendlyName": "ms-resource:loc.friendlyName", + "instanceNameFormat": "ms-resource:loc.instanceNameFormat", + "description": "ms-resource:loc.description", + "author": "Microsoft Corporation", + "category": "Package", + "helpUrl": "https://aka.ms/msix-cicd", + "helpMarkDown": "ms-resource:loc.helpMarkDown", + "execution": { + "Node10": { + "target": "index.js", + "platforms": [ + "windows" + ] + } + }, + "version": { + "Major": 1, + "Minor": 0, + "Patch": 0 + }, + "minimumAgentVersion": "1.95.0", + "inputs": [ + { + "name": "package", + "type": "string", + "label": "ms-resource:loc.input.label.package", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.package" + }, + { + "name": "vhdxOutputPath", + "type": "string", + "label": "ms-resource:loc.input.label.vhdxOutputPath", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.vhdxOutputPath" + }, + { + "name": "vhdxSize", + "type": "string", + "label": "ms-resource:loc.input.label.vhdxSize", + "defaultValue": "100", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.vhdxSize" + } + ] +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixAppAttach/tsconfig.json b/tools/pipelines-tasks/MsixAppAttach/tsconfig.json new file mode 100644 index 000000000..3c43903cf --- /dev/null +++ b/tools/pipelines-tasks/MsixAppAttach/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json" +} diff --git a/tools/pipelines-tasks/MsixPackaging/GetMSBuildPath.ps1 b/tools/pipelines-tasks/MsixPackaging/GetMSBuildPath.ps1 new file mode 100644 index 000000000..867871894 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/GetMSBuildPath.ps1 @@ -0,0 +1,9 @@ +[CmdletBinding()] +param([string]$PreferredVersion, [string]$Architecture) + +# The MSBuild helpers need the VSTS Task SDK. +# Import it before so it is available. +Import-Module $PSScriptRoot/ps_modules/VstsTaskSdk +Import-Module $PSScriptRoot/MSBuildHelpers/MSBuildHelpers + +return Select-MSBuildPath -PreferredVersion $PreferredVersion -Architecture $Architecture 3>$null 6>$null \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/ArgumentFunctions.ps1 b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/ArgumentFunctions.ps1 new file mode 100644 index 000000000..3d53ac104 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/ArgumentFunctions.ps1 @@ -0,0 +1,47 @@ +function Format-MSBuildArguments { + [CmdletBinding()] + param( + [string]$MSBuildArguments, + [string]$Platform, + [string]$Configuration, + [string]$VSVersion, + [switch]$MaximumCpuCount) + + Trace-VstsEnteringInvocation $MyInvocation + try { + if ($Platform) { + Test-MSBuildParam $Platform 'Platform' + $MSBuildArguments = "$MSBuildArguments /p:platform=`"$Platform`"" + } + + if ($Configuration) { + Test-MSBuildParam $Configuration 'Configuration' + $MSBuildArguments = "$MSBuildArguments /p:configuration=`"$Configuration`"" + } + + if ($VSVersion) { + $MSBuildArguments = "$MSBuildArguments /p:VisualStudioVersion=`"$VSVersion`"" + } + + if ($MaximumCpuCount) { + $MSBuildArguments = "$MSBuildArguments /m" + } + + $userAgent = Get-VstsTaskVariable -Name AZURE_HTTP_USER_AGENT + if ($userAgent) { + $MSBuildArguments = "$MSBuildArguments /p:_MSDeployUserAgent=`"$userAgent`"" + } + + $MSBuildArguments + } finally { + Trace-VstsLeavingInvocation $MyInvocation + } +} + +function Test-MSBuildParam ([string]$msbuildParam, [string]$parameterName) +{ + if ($msBuildParam -match '[<>*|:\/&%"#?]') + { + throw "The value of MSBuild parameter '$parameterName' ($msBuildParam) contains an invalid character. The value of $parameterName may not contain any of the following characters: < > * | : \ / & % `" # ?" + } +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/InvokeFunctions.ps1 b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/InvokeFunctions.ps1 new file mode 100644 index 000000000..2d3a57e33 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/InvokeFunctions.ps1 @@ -0,0 +1,176 @@ +######################################## +# Public functions. +######################################## +function Invoke-BuildTools { + [CmdletBinding()] + param( + [switch]$NuGetRestore, + [string[]]$SolutionFiles, + [string]$MSBuildLocation, # TODO: Switch MSBuildLocation to mandatory. Both callers (MSBuild and VSBuild task) throw prior to reaching here if MSBuild cannot be resolved. + [string]$MSBuildArguments, + [switch]$Clean, + [switch]$NoTimelineLogger, + [switch]$CreateLogFile, + [string]$LogFileVerbosity) + + Trace-VstsEnteringInvocation $MyInvocation + try { + foreach ($file in $SolutionFiles) { + if ($NuGetRestore) { + Invoke-NuGetRestore -File $file + } + + $splat = @{ } + + if ($LogFileVerbosity) { + $splat["LogFileVerbosity"] = $LogFileVerbosity + } + + if ($Clean) { + if ($CreateLogFile) { + $splat["LogFile"] = "$file-clean.log" + } + Invoke-MSBuild -ProjectFile $file -Targets Clean -MSBuildPath $MSBuildLocation -AdditionalArguments $MSBuildArguments -NoTimelineLogger:$NoTimelineLogger @splat + } + + # If we cleaned and passed /t targets, we don't need to run them again + if (!$Clean -or $MSBuildArguments -notmatch "[/-]t(arget)?:\S+") { + if ($CreateLogFile) { + $splat["LogFile"] = "$file.log" + } + Invoke-MSBuild -ProjectFile $file -MSBuildPath $MSBuildLocation -AdditionalArguments $MSBuildArguments -NoTimelineLogger:$NoTimelineLogger @splat + } + } + } finally { + Trace-VstsLeavingInvocation $MyInvocation + } +} + +######################################## +# Private functions. +######################################## +function Invoke-MSBuild { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, Position = 1)] + [string]$ProjectFile, + [string]$Targets, + [string]$LogFile, + [string]$LogFileVerbosity, + [switch]$NoTimelineLogger, + [string]$MSBuildPath, # TODO: Switch MSBuildPath to mandatory. Both callers (MSBuild and VSBuild task) throw prior to reaching here if MSBuild cannot be resolved. + [string]$AdditionalArguments) + + Trace-VstsEnteringInvocation $MyInvocation + try { + # Get the MSBuild path. + if (!$MSBuildPath) { + $MSBuildPath = Get-MSBuildPath # TODO: Delete this condition block. Both callers (MSBuild and VSBuild task) throw prior to reaching here if MSBuild cannot be resolved. + } else { + $MSBuildPath = [System.Environment]::ExpandEnvironmentVariables($MSBuildPath) + if ($MSBuildPath -notlike '*msbuild.exe') { + $MSBuildPath = [System.IO.Path]::Combine($MSBuildPath, 'msbuild.exe') + } + } + + # Validate the path exists. + $null = Assert-VstsPath -LiteralPath $MSBuildPath -PathType Leaf + + # Don't show the logo and do not allow node reuse so all child nodes are shut down once the master + # node has completed build orchestration. + $arguments = "`"$ProjectFile`" /nologo /nr:false" + + # Add the targets if specified. + if ($Targets) { + $arguments = "$arguments /t:`"$Targets`"" + } + + # If a log file was specified then hook up the default file logger. + if ($LogFile) { + $arguments = "$arguments /fl /flp:`"logfile=$LogFile;verbosity=$LogFileVerbosity`"" + } + + # Start the detail timeline. + $detailId = '' + if (!$NoTimelineLogger) { + $detailId = [guid]::NewGuid() + $detailName = Get-VstsLocString -Key MSB_Build0 -ArgumentList ([System.IO.Path]::GetFileName($ProjectFile)) + $detailStartTime = [datetime]::UtcNow.ToString('O') + Write-VstsLogDetail -Id $detailId -Type Process -Name $detailName -Progress 0 -StartTime $detailStartTime -State Initialized -AsOutput + } + + # Store the solution folder so we can provide solution-relative paths (for now) for the project events. + $solutionDirectory = [System.IO.Path]::GetDirectoryName($ProjectFile) + + # Hook up the custom logger. + $loggerAssembly = "$PSScriptRoot\Microsoft.TeamFoundation.DistributedTask.MSBuild.Logger.dll" + Assert-VstsPath -LiteralPath $loggerAssembly -PathType Leaf + $arguments = "$arguments /dl:CentralLogger,`"$loggerAssembly`";`"RootDetailId=$($detailId)|SolutionDir=$($solutionDirectory)`"*ForwardingLogger,`"$loggerAssembly`"" + + # Append additional arguments. + if ($AdditionalArguments) { + $arguments = "$arguments $AdditionalArguments" + } + + $global:LASTEXITCODE = '' + try { + # Invoke MSBuild. + Invoke-VstsTool -FileName $MSBuildPath -Arguments $arguments -RequireExitCodeZero + if ($LASTEXITCODE -ne 0) { + Write-VstsSetResult -Result Failed -DoNotThrow + } + } finally { + # Complete the detail timeline. + if (!$NoTimelineLogger) { + if ($LASTEXITCODE -ne 0) { + $detailResult = 'Failed' + } else { + $detailResult = 'Succeeded' + } + + $detailFinishTime = [datetime]::UtcNow.ToString('O') + Write-VstsLogDetail -Id $detailId -FinishTime $detailFinishTime -Progress 100 -State Completed -Result $detailResult -AsOutput + } + + if ($LogFile) { + if (Test-Path -Path $LogFile) { + Write-Host "##vso[task.uploadfile]$LogFile" + } else { + Write-Verbose "Skipping upload of '$LogFile' since it does not exist." + } + } + } + } finally { + Trace-VstsLeavingInvocation $MyInvocation + } +} + +function Invoke-NuGetRestore { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, Position = 1)] + [string]$File) + + Trace-VstsEnteringInvocation $MyInvocation + try { + Write-Warning (Get-VstsLocString -Key MSB_RestoreNuGetPackagesDeprecated) + try { + $nuGetPath = Assert-VstsPath -LiteralPath "$(Get-VstsTaskVariable -Name Agent.HomeDirectory -Require)\externals\nuget\NuGet.exe" -PathType Leaf -PassThru + } catch { + # Temporary fallback logic for legacy Windows agent. + $nuGetPath = Assert-VstsPath -LiteralPath "$(Get-VstsTaskVariable -Name Agent.HomeDirectory -Require)\Agent\Worker\Tools\NuGet.exe" -PathType Leaf -PassThru + } + + if ($env:NUGET_EXTENSIONS_PATH) { + Write-Host (Get-VstsLocString -Key MSB_DetectedNuGetExtensionsLoaderPath0 -ArgumentList $env:NUGET_EXTENSIONS_PATH) + } + + $directory = [System.IO.Path]::GetDirectoryName($file) + Invoke-VstsTool -FileName $nuGetPath -Arguments "restore `"$file`" -NonInteractive" -WorkingDirectory $directory -RequireExitCodeZero + if ($LASTEXITCODE -ne 0) { + Write-VstsSetResult -Result Failed -DoNotThrow + } + } finally { + Trace-VstsLeavingInvocation $MyInvocation + } +} diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/MSBuildHelpers.psm1 b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/MSBuildHelpers.psm1 new file mode 100644 index 000000000..6fe14b182 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/MSBuildHelpers.psm1 @@ -0,0 +1,17 @@ +[CmdletBinding()] +param() +Import-VstsLocStrings "$PSScriptRoot\module.json" +. $PSScriptRoot\ArgumentFunctions +. $PSScriptRoot\InvokeFunctions +. $PSScriptRoot\PathFunctions +Export-ModuleMember -Function @( + # Argument functions. + 'Format-MSBuildArguments' + # Invoke functions. + 'Invoke-BuildTools' + # Path functions. + 'Get-MSBuildPath' + 'Get-SolutionFiles' + 'Get-VisualStudio' + 'Select-MSBuildPath' +) \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/PathFunctions.ps1 b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/PathFunctions.ps1 new file mode 100644 index 000000000..2bd38689d --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/PathFunctions.ps1 @@ -0,0 +1,309 @@ +######################################## +# Public functions. +######################################## +$script:visualStudioCache = @{ } + +######################################## +# Public functions. +######################################## +function Get-MSBuildPath { + [CmdletBinding()] + param( + [string]$Version, + [string]$Architecture) + + Trace-VstsEnteringInvocation $MyInvocation + try { + # Only attempt to find Microsoft.Build.Utilities.Core.dll from a VS 15 Willow install + # when "15.0" or latest is specified. In 15.0, the method GetPathToBuildToolsFile(...) + # has regressed. When it is called for a version that is not found, the latest version + # found is returned instead. Same for "16.0" + [System.Reflection.Assembly]$msUtilities = $null + if (($Version -eq "16.0" -or !$Version) -and # !$Version indicates "latest" + ($visualStudio16 = Get-VisualStudio 16) -and + $visualStudio16.installationPath) { + + $msbuildUtilitiesPath = [System.IO.Path]::Combine($visualStudio16.installationPath, "MSBuild\Current\Bin\Microsoft.Build.Utilities.Core.dll") + if (Test-Path -LiteralPath $msbuildUtilitiesPath -PathType Leaf) { + Write-Verbose "Loading $msbuildUtilitiesPath" + $msUtilities = [System.Reflection.Assembly]::LoadFrom($msbuildUtilitiesPath) + } + } + elseif (($Version -eq "15.0" -or !$Version) -and # !$Version indicates "latest" + ($visualStudio15 = Get-VisualStudio 15) -and + $visualStudio15.installationPath) { + + $msbuildUtilitiesPath = [System.IO.Path]::Combine($visualStudio15.installationPath, "MSBuild\15.0\Bin\Microsoft.Build.Utilities.Core.dll") + if (Test-Path -LiteralPath $msbuildUtilitiesPath -PathType Leaf) { + Write-Verbose "Loading $msbuildUtilitiesPath" + $msUtilities = [System.Reflection.Assembly]::LoadFrom($msbuildUtilitiesPath) + } + } + + # Fallback to searching the GAC. + if (!$msUtilities) { + $msbuildUtilitiesAssemblies = @( + "Microsoft.Build.Utilities.Core, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" + "Microsoft.Build.Utilities.Core, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" + "Microsoft.Build.Utilities.v12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" + "Microsoft.Build.Utilities.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" + ) + + # Attempt to load a Microsoft build utilities DLL. + $index = 0 + [System.Reflection.Assembly]$msUtilities = $null + while (!$msUtilities -and $index -lt $msbuildUtilitiesAssemblies.Length) { + Write-Verbose "Loading $($msbuildUtilitiesAssemblies[$index])" + try { + $msUtilities = [System.Reflection.Assembly]::Load((New-Object System.Reflection.AssemblyName($msbuildUtilitiesAssemblies[$index]))) + } catch [System.IO.FileNotFoundException] { + Write-Verbose "Not found." + } + + $index++ + } + } + + [string]$msBuildPath = $null + + # Default to x86 architecture if not specified. + if (!$Architecture) { + $Architecture = "x86" + } + + if ($msUtilities -ne $null) { + [type]$t = $msUtilities.GetType('Microsoft.Build.Utilities.ToolLocationHelper') + if ($t -ne $null) { + # Attempt to load the method info for GetPathToBuildToolsFile. This method + # is available in the 16.0, 15.0, 14.0, and 12.0 utilities DLL. It is not available + # in the 4.0 utilities DLL. + [System.Reflection.MethodInfo]$mi = $t.GetMethod( + "GetPathToBuildToolsFile", + [type[]]@( [string], [string], $msUtilities.GetType("Microsoft.Build.Utilities.DotNetFrameworkArchitecture") )) + if ($mi -ne $null -and $mi.GetParameters().Length -eq 3) { + $versions = "16.0", "15.0", "14.0", "12.0", "4.0" + if ($Version) { + $versions = @( $Version ) + } + + # Translate the architecture parameter into the corresponding value of the + # DotNetFrameworkArchitecture enum. Parameter three of the target method info + # takes this enum. Leverage parameter three to get to the enum's type info. + $param3 = $mi.GetParameters()[2] + $archValues = [System.Enum]::GetValues($param3.ParameterType) + [object]$archValue = $null + if ($Architecture -eq 'x86') { + $archValue = $archValues.GetValue(1) # DotNetFrameworkArchitecture.Bitness32 + } elseif ($Architecture -eq 'x64') { + $archValue = $archValues.GetValue(2) # DotNetFrameworkArchitecture.Bitness64 + } else { + $archValue = $archValues.GetValue(1) # DotNetFrameworkArchitecture.Bitness32 + } + + # Attempt to resolve the path for each version. + $versionIndex = 0 + while (!$msBuildPath -and $versionIndex -lt $versions.Length) { + $msBuildPath = $mi.Invoke( + $null, + @( 'msbuild.exe' # string fileName + $versions[$versionIndex] # string toolsVersion + $archValue )) + $versionIndex++ + } + } elseif (!$Version -or $Version -eq "4.0") { + # Attempt to load the method info GetPathToDotNetFrameworkFile. This method + # is available in the 4.0 utilities DLL. + $mi = $t.GetMethod( + "GetPathToDotNetFrameworkFile", + [type[]]@( [string], $msUtilities.GetType("Microsoft.Build.Utilities.TargetDotNetFrameworkVersion"), $msUtilities.GetType("Microsoft.Build.Utilities.DotNetFrameworkArchitecture") )) + if ($mi -ne $null -and $mi.GetParameters().Length -eq 3) { + # Parameter two of the target method info takes the TargetDotNetFrameworkVersion + # enum. Leverage parameter two to get the enum's type info. + $param2 = $mi.GetParameters()[1]; + $frameworkVersionValues = [System.Enum]::GetValues($param2.ParameterType); + + # Translate the architecture parameter into the corresponding value of the + # DotNetFrameworkArchitecture enum. Parameter three of the target method info + # takes this enum. Leverage parameter three to get to the enum's type info. + $param3 = $mi.GetParameters()[2]; + $archValues = [System.Enum]::GetValues($param3.ParameterType); + [object]$archValue = $null + if ($Architecture -eq "x86") { + $archValue = $archValues.GetValue(1) # DotNetFrameworkArchitecture.Bitness32 + } elseif ($Architecture -eq "x64") { + $archValue = $archValues.GetValue(2) # DotNetFrameworkArchitecture.Bitness64 + } else { + $archValue = $archValues.GetValue(1) # DotNetFrameworkArchitecture.Bitness32 + } + + # Attempt to resolve the path. + $msBuildPath = $mi.Invoke( + $null, + @( "msbuild.exe" # string fileName + $frameworkVersionValues.GetValue($frameworkVersionValues.Length - 1) # enum TargetDotNetFrameworkVersion.VersionLatest + $archValue )) + } + } + } + } + + if ($msBuildPath -and (Test-Path -LiteralPath $msBuildPath -PathType Leaf)) { + Write-Verbose "MSBuild: $msBuildPath" + $msBuildPath + } + } finally { + Trace-VstsLeavingInvocation $MyInvocation + } +} + +function Get-SolutionFiles { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Solution) + + Trace-VstsEnteringInvocation $MyInvocation + try { + if ($Solution.Contains("*") -or $Solution.Contains("?")) { + $solutionFiles = Find-VstsFiles -LegacyPattern $Solution + if (!$solutionFiles.Count) { + throw (Get-VstsLocString -Key MSB_SolutionNotFoundUsingSearchPattern0 -ArgumentList $Solution) + } + } else { + $solutionFiles = ,$Solution + } + + $solutionFiles + } finally { + Trace-VstsLeavingInvocation $MyInvocation + } +} + +function Get-VisualStudio { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ValidateSet(15, 16)] + [int]$MajorVersion) + + Trace-VstsEnteringInvocation $MyInvocation + try { + if (!$script:visualStudioCache.ContainsKey("$MajorVersion.0")) { + try { + # Query for the latest $MajorVersion.* version. + # + # Note, the capability is registered as VisualStudio_16.0, however the actual version + # may be something like 16.2. + Write-Verbose "Getting latest Visual Studio $MajorVersion setup instance." + $output = New-Object System.Text.StringBuilder + Invoke-VstsTool -FileName "$PSScriptRoot\vswhere.exe" -Arguments "-version [$MajorVersion.0,$($MajorVersion+1).0) -latest -format json" -RequireExitCodeZero 2>&1 | + ForEach-Object { + if ($_ -is [System.Management.Automation.ErrorRecord]) { + Write-Verbose "STDERR: $($_.Exception.Message)" + } + else { + Write-Verbose $_ + $null = $output.AppendLine($_) + } + } + $script:visualStudioCache["$MajorVersion.0"] = (ConvertFrom-Json -InputObject $output.ToString()) | + Select-Object -First 1 + if (!$script:visualStudioCache["$MajorVersion.0"]) { + # Query for the latest $MajorVersion.* BuildTools. + # + # Note, whereas VS 16.x version number is always 16.0.*, BuildTools does not follow the + # the same scheme. It appears to follow the 16..* versioning scheme. + Write-Verbose "Getting latest BuildTools 16 setup instance." + $output = New-Object System.Text.StringBuilder + Invoke-VstsTool -FileName "$PSScriptRoot\vswhere.exe" -Arguments "-version [$MajorVersion.0,$($MajorVersion+1).0) -products Microsoft.VisualStudio.Product.BuildTools -latest -format json" -RequireExitCodeZero 2>&1 | + ForEach-Object { + if ($_ -is [System.Management.Automation.ErrorRecord]) { + Write-Verbose "STDERR: $($_.Exception.Message)" + } + else { + Write-Verbose $_ + $null = $output.AppendLine($_) + } + } + $script:visualStudioCache["$MajorVersion.0"] = (ConvertFrom-Json -InputObject $output.ToString()) | + Select-Object -First 1 + } + } catch { + Write-Verbose ($_ | Out-String) + $script:visualStudioCache["$MajorVersion.0"] = $null + } + } + + return $script:visualStudioCache["$MajorVersion.0"] + } finally { + Trace-VstsLeavingInvocation $MyInvocation + } +} +function Select-MSBuildPath { + [CmdletBinding()] + param( + [string]$Method, + [string]$Location, + [string]$PreferredVersion, + [string]$Architecture) + + Trace-VstsEnteringInvocation $MyInvocation + try { + # Default the msbuildLocationMethod if not specified. The input msbuildLocationMethod + # was added to the definition after the input msbuildLocation. + if ("$Method".ToUpperInvariant() -ne 'LOCATION' -and "$Method".ToUpperInvariant() -ne 'VERSION') { + # Infer the msbuildLocationMethod based on the whether msbuildLocation is specified. + if ($Location) { + $Method = 'location' + } else { + $Method = 'version' + } + + Write-Verbose "Defaulted MSBuild location method to: $Method" + } + + if ("$Method".ToUpperInvariant() -eq 'LOCATION') { + # Return the location. + if ($Location) { + return $Location + } + + # Fallback to version lookup. + Write-Verbose "Location not specified. Looking up by version instead." + } + + $specificVersion = $PreferredVersion -and $PreferredVersion -ne 'latest' + $versions = "16.0", '15.0', '14.0', '12.0', '4.0' | Where-Object { $_ -ne $PreferredVersion } + + # Look for a specific version of MSBuild. + if ($specificVersion) { + if (($path = Get-MSBuildPath -Version $PreferredVersion -Architecture $Architecture)) { + return $path + } + + # Attempt to fallback. + Write-Verbose "Specified version '$PreferredVersion' and architecture '$Architecture' not found. Attempting to fallback." + } + + # Look for the latest version of MSBuild. + foreach ($version in $versions) { + if (($path = Get-MSBuildPath -Version $version -Architecture $Architecture)) { + # Warn falling back. + if ($specificVersion) { + Write-Warning (Get-VstsLocString -Key 'MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2' -ArgumentList $PreferredVersion, $Architecture, $version) + } + + return $path + } + } + + # Error. Not found. + if ($specificVersion) { + Write-Error (Get-VstsLocString -Key 'MSB_MSBuildNotFoundVersion0Architecture1' -ArgumentList $PreferredVersion, $Architecture) + } else { + Write-Error (Get-VstsLocString -Key 'MSB_MSBuildNotFound') + } + } finally { + Trace-VstsLeavingInvocation $MyInvocation + } +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/de-de/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/de-de/resources.resjson new file mode 100644 index 000000000..774392d7a --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/de-de/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "Build {0}", + "loc.messages.MSB_BuildToolNotFound": "MSBuild oder xbuild(Mono) wurde auf dem Mac-/Linux-Agent nicht gefunden.", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "Der Pfad des NuGet-Extensionladeprogramms wurde erkannt. Die Umgebungsvariable \"NUGET_EXTENSIONS_PATH\" ist auf \"{0}\" festgelegt.", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "MSBuild-Version \"15.0\" wurde bei der Architektur \"{0}\" nicht gefunden. Überprüfen Sie, ob der Architektureingabewert korrekt ist und ob Visual Studio 2017 installiert ist. MSBuild-Version \"15.0\" ist enthalten, wenn Visual Studio 2017 installiert ist.", + "loc.messages.MSB_MSBuildNotFound": "MSBuild wurde nicht gefunden. Versuchen Sie, den Speicherort von \"msbuild.exe\" anzugeben oder Visual Studio zu installieren. MSBuild ist enthalten, wenn Visual Studio installiert ist.", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "MSBuild wurde für Version \"{0}\" und Architektur \"{1}\" nicht gefunden. Testen Sie eine andere Kombination aus Version und Architektur, geben Sie einen Speicherort an, oder installieren Sie die entsprechende Version von Visual Studio. MSBuild ist enthalten, wenn Visual Studio installiert ist.", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "Die Option \"NuGet-Pakete wiederherstellen\" ist veraltet. Fügen Sie Ihrer Builddefinition zum Wiederherstellen von NuGet-Paketen im Build einen Task für den NuGet-Toolinstaller hinzu.", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "Die Lösung wurde mithilfe des Suchmusters \"{0}\" nicht gefunden.", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "Die MSBuild-Version \"{0}\" für Architektur \"{1}\" wurde nicht gefunden. Die Version \"{2}\" wird verwendet." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/en-US/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/en-US/resources.resjson new file mode 100644 index 000000000..7f4ea7930 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/en-US/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "Build {0}", + "loc.messages.MSB_BuildToolNotFound": "MSBuild or xbuild(Mono) were not found on the Mac/Linux agent.", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "Detected NuGet extensions loader path. Environment variable NUGET_EXTENSIONS_PATH is set to '{0}'.", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "MSBuild version '15.0' was not found for architecture '{0}'. Verify the architecture input value is correct and verify Visual Studio 2017 is installed. MSBuild version '15.0' is included when Visual Studio 2017 is installed.", + "loc.messages.MSB_MSBuildNotFound": "MSBuild was not found. Try specifying the location to msbuild.exe or install Visual Studio. MSBuild is included when Visual Studio is installed.", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "MSBuild was not found for version '{0}' and architecture '{1}'. Try a different version/architecture combination, specify a location, or install the appropriate version of Visual Studio. MSBuild is included when Visual Studio is installed.", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "The 'Restore NuGet Packages' option is deprecated. To restore NuGet packages in your build, add a NuGet Tool Installer task to your build definition.", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "Solution not found using search pattern '{0}'.", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "Unable to find MSBuild version '{0}' for architecture '{1}'. Falling back to version '{2}'." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/es-es/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/es-es/resources.resjson new file mode 100644 index 000000000..630a2a1fb --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/es-es/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "Compilación {0}", + "loc.messages.MSB_BuildToolNotFound": "No se encontró MSBuild o xbuild (Mono) en el agente para Mac o Linux.", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "Se detectó la ruta de acceso del cargador de extensiones de NuGet. La variable de entorno NUGET_EXTENSIONS_PATH está establecida en '{0}'.", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "No se encontró la versión de MSBuild \"15.0\" para la arquitectura \"{0}\". Compruebe que el valor de entrada de la arquitectura es correcto y compruebe que Visual Studio 2017 está instalado. La versión de MSBuild \"15.0\" se incluyó con la instalación de Visual Studio 2017.", + "loc.messages.MSB_MSBuildNotFound": "No se encontró MSBuild. Pruebe a especificar la ubicación de msbuild.exe o instale Visual Studio. MSBuild se incluyó con la instalación de Visual Studio.", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "No se encontró MSBuild para la versión \"{0}\" y la arquitectura \"{1}\". Pruebe una combinación diferente de versión/arquitectura, especifique una ubicación o instale la versión adecuada de Visual Studio. MSBuild se incluyó con la instalación de Visual Studio.", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "La opción \"Restaurar paquetes de NuGet\" está en desuso. Para restaurar paquetes de NuGet en la compilación, agregue una tarea del instalador de la herramienta NuGet a la definición de compilación.", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "No se encontró la solución con el patrón de búsqueda '{0}'.", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "No se encuentra la versión de MSBuild \"{0}\" para la arquitectura \"{1}\". Revirtiendo a la versión \"{2}\"." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/fr-fr/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/fr-fr/resources.resjson new file mode 100644 index 000000000..3f029685b --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/fr-fr/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "Build {0}", + "loc.messages.MSB_BuildToolNotFound": "MSBuild ou xbuild (Mono) sont introuvables sur l'agent Mac/Linux.", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "Le chemin du chargeur d'extensions NuGet a été détecté. La variable d'environnement NUGET_EXTENSIONS_PATH a la valeur '{0}'.", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "MSBuild version '15.0' est introuvable pour l'architecture '{0}'. Vérifiez que la valeur d'entrée de l'architecture est correcte et que Visual Studio 2017 est installé. MSBuild version '15.0' est inclus durant l'installation de Visual Studio 2017.", + "loc.messages.MSB_MSBuildNotFound": "MSBuild est introuvable. Essayez de spécifier l'emplacement à msbuild.exe, ou installez Visual Studio. MSBuild est inclus durant l'installation de Visual Studio.", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "MSBuild est introuvable pour la version '{0}' et l'architecture '{1}'. Essayez une autre combinaison version/architecture, spécifiez un emplacement, ou installez la version appropriée de Visual Studio. MSBuild est inclus durant l'installation de Visual Studio.", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "L'option Restaurer des packages NuGet est dépréciée. Pour restaurer des packages NuGet dans votre build, ajoutez une tâche Programme d'installation de l'outil NuGet à votre définition de build.", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "Solution introuvable à l'aide du modèle de recherche '{0}'.", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "MSBuild version '{0}' est introuvable pour l'architecture '{1}'. Retour à la version '{2}'." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/it-IT/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/it-IT/resources.resjson new file mode 100644 index 000000000..56e3835a7 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/it-IT/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "Compilazione {0}", + "loc.messages.MSB_BuildToolNotFound": "MSBuild o xbuild (Mono) non è stato trovato nell'agente Mac/Linux.", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "Il percorso del caricatore delle estensioni NuGet è stato rilevato. La variabile di ambiente NUGET_EXTENSIONS_PATH è impostata su '{0}'.", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "La versione '15.0' di MSBuild non è stata trovata per l'architettura '{0}'. Verificare che il valore di input dell'architettura sia corretto e che Visual Studio 2017 sia installato. La versione '15.0' di MSBuild è inclusa quando si installa Visual Studio 2017.", + "loc.messages.MSB_MSBuildNotFound": "MSBuild non è stato trovato. Provare a specificare il percorso di msbuild.exe o installare Visual Studio. MSBuild è incluso quando Visual Studio è installato.", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "MSBuild non è stato trovato per la versione '{0}' e l'architettura '{1}'. Provare con una combinazione diversa di versione/architettura, specificare un percorso oppure installare la versione appropriata di Visual Studio. MSBuild è incluso quando si installa Visual Studio.", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "L'opzione 'Ripristina pacchetti NuGet' è deprecata. Per ripristinare i pacchetti NuGet nella compilazione, aggiungere un'attività Programma di installazione strumento NuGet alla definizione di compilazione.", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "Non sono state trovate soluzioni con il criterio di ricerca '{0}'.", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "La versione '{0}' di MSBuild non è stata trovata per l'architettura '{1}'. Verrà eseguito il fallback alla versione '{2}'." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/ja-jp/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/ja-jp/resources.resjson new file mode 100644 index 000000000..df4b39660 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/ja-jp/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "ビルド: {0}", + "loc.messages.MSB_BuildToolNotFound": "MSBuild または xbuild(Mono) が Mac/Linux エージェント上に見つかりませんでした。", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "NuGet 拡張機能のローダー パスが検出されました。環境変数 NUGET_EXTENSIONS_PATH が '{0}' に設定されています。", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "MSBuild バージョン '15.0' がアーキテクチャ '{0}' にありません。アーキテクチャの入力値が正しいことを確認し、Visual Studio 2017 がインストールされていることを確認してください。MSBuild バージョン '15.0' は、Visual Studio 2017 のインストール時にインストールされます。", + "loc.messages.MSB_MSBuildNotFound": "MSBuild が見つかりませんでした。msbuild.exe の場所を指定するか、Visual Studio をインストールしてください。MSBuild は Visual Studio のインストール時にインストールされます。", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "バージョン '{0}' およびアーキテクチャ '{1}' の MSBuild が見つかりませんでした。バージョン/アーキテクチャの別の組み合わせを試し、場所を指定し、適切なバージョンの Visual Studio をインストールしてください。MSBuild は Visual Studio のインストール時にインストールされます。", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "'NuGet パッケージの復元' オプションは非推奨になりました。ビルド内の NuGet パッケージを復元するには、NuGet Tool インストーラーのタスクをビルド定義に追加します。", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "検索パターン '{0}' を使ってソリューションが見つかりませんでした。", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "アーキテクチャ '{1}' の MSBuild バージョン '{0}' が見つかりません。バージョン '{2}' にフォールバックします。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/ko-KR/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/ko-KR/resources.resjson new file mode 100644 index 000000000..3d212a39a --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/ko-KR/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "빌드 {0}", + "loc.messages.MSB_BuildToolNotFound": "Mac/Linux 에이전트에서 MSBuild 또는 xbuild(Mono)를 찾을 수 없습니다.", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "NuGet 확장 로드 경로가 검색되었습니다. 환경 변수 NUGET_EXTENSIONS_PATH는 '{0}'(으)로 설정되어 있습니다.", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "아키텍처 '{0}'에 대해 MSBuild 버전 '15.0'을 찾을 수 없습니다. 아키텍처 입력 값이 올바른지 확인하고 Visual Studio 2017이 설치되어 있는지 확인하세요. Visual Studio 2017을 설치할 때 MSBuild 버전 '15.0'이 포함됩니다.", + "loc.messages.MSB_MSBuildNotFound": "MSBuild를 찾을 수 없습니다. msbuild.exe에 대한 위치를 지정하거나 Visual Studio를 설치하세요. Visual Studio를 설치할 때 MSBuild가 포함됩니다.", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "버전 '{0}' 및 아키텍처 '{1}'에 대해 MSBuild를 찾을 수 없습니다. 다른 버전/아키텍처 조합을 시도해 보거나, 위치를 지정하거나, 적합한 버전의 Visual Studio를 설치해 보세요. Visual Studio를 설치할 때 MSBuild가 포함됩니다.", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "'NuGet 패키지 복원' 옵션은 사용되지 않습니다. 빌드에서 NuGet 패키지를 복원하려면 빌드 정의에 NuGet 도구 설치 관리자 작업을 추가하세요.", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "검색 패턴 '{0}'을(를) 사용하여 솔루션을 찾을 수 없습니다.", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "아키텍처 '{1}'에 대해 MSBuild 버전 '{0}'을(를) 찾을 수 없습니다. '{2}' 버전을 대신 사용합니다." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/ru-RU/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/ru-RU/resources.resjson new file mode 100644 index 000000000..7cc74efec --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/ru-RU/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "Сборка {0}", + "loc.messages.MSB_BuildToolNotFound": "В агенте Mac/Linux не удалось найти MSBuild или xbuild (Mono).", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "Обнаружен путь к загрузчику расширений NuGet. Для переменной среды NUGET_EXTENSIONS_PATH задано значение \"{0}\".", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "Не найдено средство MSBuild 15.0 для архитектуры \"{0}\". Проверьте правильность входного значения архитектуры и убедитесь, что среда Visual Studio 2017 установлена. Средство MSBuild версии 15.0 включено в установку Visual Studio 2017.", + "loc.messages.MSB_MSBuildNotFound": "Не найдено средство MSBuild. Укажите расположение msbuild.exe или установите Visual Studio. Средство MSBuild включено в установку Visual Studio.", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "Не найдено средство MSBuild \"{0}\" для архитектуры \"{1}\". Попробуйте использовать другую комбинацию версии и архитектуры, укажите расположение или установите соответствующую версию Visual Studio. Средство MSBuild включено в установку Visual Studio.", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "Параметр \"Восстановить пакеты NuGet\" является устаревшим. Чтобы восстановить пакеты NuGet в сборке, добавьте задание установщика средства NuGet в определение сборки.", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "Не удалось найти решение по шаблону поиска \"{0}\".", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "Не удалось найти MSBuild версии \"{0}\" для архитектуры \"{1}\". Выполняется откат к версии {2}." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/zh-CN/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/zh-CN/resources.resjson new file mode 100644 index 000000000..e48de6401 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/zh-CN/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "生成 {0}", + "loc.messages.MSB_BuildToolNotFound": "Mac/Linux 代理上找不到 MSBuild 或 xbuild(Mono)。", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "已检测到 NuGet 扩展加载程序路径。将环境变量 NUGET_EXTENSIONS_PATH 设置为“{0}”。", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "找不到适用于体系结构“{0}”的 MSBuild 版本 \"15.0\"。请验证体系结构输入值正确,且已安装 Visual Studio 2017。安装 Visual Studio 2017 时 MSBuild 版本 \"15.0\" 也包括在内。", + "loc.messages.MSB_MSBuildNotFound": "找不到 MSBuild。尝试指定 msbuild.exe 的位置或安装 Visual Studio。安装 Visual Studio 时 MSBuild 也包括在内。", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "找不到适用于体系结构“{1}”的 MSBuild 版本“{0}”。请尝试其他版本/体系结构组合,指定一个位置或安装合适版本的 Visual Studio。安装 Visual Studio 时 MSBuild 也包括在内。", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "“还原 NuGet 包”选项已弃用。若要在生成中还原 NuGet 包,请将 NuGet 工具安装程序任务添加到生成定义中。", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "使用搜索模式“{0}”找不到解决方案。", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "找不到适用于体系结构“{1}”的 MSBuild 版本“{0}”。正在回退到版本“{2}”。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/zh-TW/resources.resjson b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/zh-TW/resources.resjson new file mode 100644 index 000000000..4cd4604c1 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/Strings/resources.resjson/zh-TW/resources.resjson @@ -0,0 +1,11 @@ +{ + "loc.messages.MSB_Build0": "組建 {0}", + "loc.messages.MSB_BuildToolNotFound": "在 Mac/Linux 代理程式上找不到 MSBuild 或 xbuild (Mono)。", + "loc.messages.MSB_DetectedNuGetExtensionsLoaderPath0": "偵測到 NuGet 擴充功能載入器路徑。環境變數 NUGET_EXTENSIONS_PATH 設定為 '{0}'。", + "loc.messages.MSB_MSBuild15NotFoundionArchitecture0": "架構 '{0}' 中找不到 MSBuild 版本 '15.0'。請確認架構輸入值是否正確,且確認是否已安裝 Visual Studio 2017。已安裝 Visual Studio 2017 時會包含 MSBuild 版本 '15.0'。", + "loc.messages.MSB_MSBuildNotFound": "找不到 MSBuild。請嘗試指定 msbuild.exe 的位置或安裝 Visual Studio。已安裝 Visual Studio 時會包含 MSBuild。", + "loc.messages.MSB_MSBuildNotFoundVersion0Architecture1": "版本 '{0}' 與架構 '{1}' 中找不到 MSBuild。請嘗試其他版本/架構組合的、指定位置,或安裝正確的 Visual Studio 版本。已安裝 Visual Studio 時會包含 MSBuild。", + "loc.messages.MSB_RestoreNuGetPackagesDeprecated": "[還原 NuGet 套件] 選項即將淘汰。若要還原組件中的 NuGet 套件,請在組建定義中新增 NuGet 工具安裝程式工作。", + "loc.messages.MSB_SolutionNotFoundUsingSearchPattern0": "使用搜尋模式 '{0}' 找不到解決方案。", + "loc.messages.MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "找不到架構 '{1}' 的 MSBuild 版本 '{0}'。切換回版本 '{2}'。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/make.json b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/make.json new file mode 100644 index 000000000..ca08cacb2 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/make.json @@ -0,0 +1,14 @@ +{ + "externals": { + "archivePackages": [ + { + "url": "https://vstsagenttools.blob.core.windows.net/tools/msbuildlogger/3/msbuildlogger.zip", + "dest": "./" + }, + { + "url": "https://vstsagenttools.blob.core.windows.net/tools/vswhere/1_0_62/vswhere.zip", + "dest": "./" + } + ] + } +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/module.json b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/module.json new file mode 100644 index 000000000..9c31ea68d --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/module.json @@ -0,0 +1,13 @@ +{ + "messages": { + "MSB_Build0" : "Build {0}", + "MSB_BuildToolNotFound": "MSBuild or xbuild(Mono) were not found on the Mac/Linux agent.", + "MSB_DetectedNuGetExtensionsLoaderPath0": "Detected NuGet extensions loader path. Environment variable NUGET_EXTENSIONS_PATH is set to '{0}'.", + "MSB_MSBuild15NotFoundionArchitecture0": "MSBuild version '15.0' was not found for architecture '{0}'. Verify the architecture input value is correct and verify Visual Studio 2017 is installed. MSBuild version '15.0' is included when Visual Studio 2017 is installed.", + "MSB_MSBuildNotFound": "MSBuild was not found. Try specifying the location to msbuild.exe or install Visual Studio. MSBuild is included when Visual Studio is installed.", + "MSB_MSBuildNotFoundVersion0Architecture1": "MSBuild was not found for version '{0}' and architecture '{1}'. Try a different version/architecture combination, specify a location, or install the appropriate version of Visual Studio. MSBuild is included when Visual Studio is installed.", + "MSB_RestoreNuGetPackagesDeprecated": "The 'Restore NuGet Packages' option is deprecated. To restore NuGet packages in your build, add a NuGet Tool Installer task to your build definition.", + "MSB_SolutionNotFoundUsingSearchPattern0": "Solution not found using search pattern '{0}'.", + "MSB_UnableToFindMSBuildVersion0Architecture1FallbackVersion2": "Unable to find MSBuild version '{0}' for architecture '{1}'. Falling back to version '{2}'." + } +} diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/msbuildhelpers.ts b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/msbuildhelpers.ts new file mode 100644 index 000000000..3c4d928cf --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/msbuildhelpers.ts @@ -0,0 +1,49 @@ +import tl = require('azure-pipelines-task-lib/task'); + +/** + * Finds the tool path for msbuild/xbuild based on specified msbuild version on Mac or Linux agent + * @param version + */ +export async function getMSBuildPath(version: string) { + let toolPath: string | undefined; + + if (version === '15.0' || version === 'latest') { + let msbuildPath: string = tl.which('msbuild', false); + if (msbuildPath) { + // msbuild found on the agent, check version + let msbuildVersion: number | undefined; + + let msbuildVersionCheckTool = tl.tool(msbuildPath); + msbuildVersionCheckTool.arg(['/version', '/nologo']); + msbuildVersionCheckTool.on('stdout', function (data) { + if (data) { + let intData = parseInt(data.toString().trim()); + if (intData && !isNaN(intData)) { + msbuildVersion = intData; + } + } + }) + await msbuildVersionCheckTool.exec(); + + if (msbuildVersion) { + // found msbuild version on the agent, check if it matches requirements + if (msbuildVersion >= 15) { + toolPath = msbuildPath; + } + } + } + } + + if (!toolPath) { + // either user selected old version of msbuild or we didn't find matching msbuild version on the agent + // fallback to xbuild + toolPath = tl.which('xbuild', false); + + if (!toolPath) { + // failed to find a version of msbuild / xbuild on the agent + throw tl.loc('MSB_BuildToolNotFound'); + } + } + + return toolPath; +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/package-lock.json b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/package-lock.json new file mode 100644 index 000000000..7ac27e8e4 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/package-lock.json @@ -0,0 +1,263 @@ +{ + "name": "msbuildhelpers", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "azure-pipelines-task-lib": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.12.2.tgz", + "integrity": "sha512-ofAdVZcL90Qv6zYcKa1vK3Wnrl2kxoKX/Idvb7RWrqHQzcJlAEjCU4UCB5y6NnSKqRSyVTIhdS6hChphpOaiMQ==", + "requires": { + "minimatch": "3.0.4", + "mockery": "^1.7.0", + "q": "^1.1.2", + "semver": "^5.1.0", + "shelljs": "^0.3.0", + "sync-request": "3.0.1", + "uuid": "^3.0.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "http-basic": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-2.5.1.tgz", + "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.6", + "http-response-object": "^1.0.0" + } + }, + "http-response-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", + "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mockery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", + "integrity": "sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8=" + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "sync-request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-3.0.1.tgz", + "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", + "requires": { + "concat-stream": "^1.4.7", + "http-response-object": "^1.0.1", + "then-request": "^2.0.1" + } + }, + "then-request": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", + "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.7", + "http-basic": "^2.5.1", + "http-response-object": "^1.1.0", + "promise": "^7.1.1", + "qs": "^6.1.0" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } +} diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/package.json b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/package.json new file mode 100644 index 000000000..f72606269 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/package.json @@ -0,0 +1,22 @@ +{ + "name": "msbuildhelpers", + "version": "1.0.0", + "description": "Azure Pipelines tasks MSBuild helpers", + "main": "msbuildhelpers.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/Microsoft/azure-pipelines-tasks.git" + }, + "author": "Microsoft Corporation", + "license": "MIT", + "bugs": { + "url": "https://github.com/Microsoft/azure-pipelines-tasks/issues" + }, + "homepage": "https://github.com/Microsoft/azure-pipelines-tasks#readme", + "dependencies": { + "azure-pipelines-task-lib": "^2.12.2" + } +} diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/tsconfig.json b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/tsconfig.json new file mode 100644 index 000000000..cf1f71f6d --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "declaration": true, + "noImplicitAny": false, + "sourceMap": false + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/typings.json b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/typings.json new file mode 100644 index 000000000..0f6f178b3 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/typings.json @@ -0,0 +1,8 @@ +{ + "name": "msbuildhelpers", + "dependencies": {}, + "globalDependencies": { + "node": "registry:dt/node#6.0.0+20160915134512", + "q": "registry:dt/q#0.0.0+20160613154756" + } +} diff --git a/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/vswhere.exe b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/vswhere.exe new file mode 100644 index 000000000..80706d852 Binary files /dev/null and b/tools/pipelines-tasks/MsixPackaging/MSBuildHelpers/vswhere.exe differ diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/de-DE/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/de-DE/resources.resjson new file mode 100644 index 000000000..03e0f55f7 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/de-DE/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "MSIX-Build und -Paket", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Erstellen und Verpacken von Windows-Apps mithilfe des MSIX-Paketformats", + "loc.instanceNameFormat": "MSIX-Build und -Paket", + "loc.input.label.outputPath": "Ausgabepfad", + "loc.input.help.outputPath": "Pfad des generierten Pakets oder Bundles", + "loc.input.label.buildSolution": "Erstellen von Lösungen mit MSBuild", + "loc.input.help.buildSolution": "Rufen Sie MSBuild auf, und erstellen Sie die Lösung von Grund auf neu. Wenn diese Option ausgewählt ist, müssen Sie einen Pfad zu der zu erstellenden Lösungsdatei angeben und die zu erstellenden Zielplattformen auswählen.", + "loc.input.label.inputDirectory": "Eingabeverzeichnis", + "loc.input.help.inputDirectory": "Der relative Pfad vom Repo-Stamm zum Verzeichnis mit den zu verpackenden Dateien.", + "loc.input.label.solution": "Zu erstellendes Projekt", + "loc.input.help.solution": "Der relative Pfad vom Repo-Stamm des Projekts bzw. der Projektmappe, der erstellt werden soll. Es können Platzhalter verwendet werden.", + "loc.input.label.clean": "Vor der Erstellung reinigen", + "loc.input.help.clean": "Führen Sie vor dem Build einen sauberen Build (/t:clean) aus.", + "loc.input.label.generateBundle": "MSIX-Bundle generieren", + "loc.input.help.generateBundle": "Generieren Sie ein MSIX-Bundle.", + "loc.input.label.buildConfiguration": "Konfiguration", + "loc.input.help.buildConfiguration": "Die Konfiguration, mit der die Lösung erstellt werden soll. Es wird nur eine Konfiguration gleich zeitig unter stützt.", + "loc.input.label.buildPlatform": "Plattform", + "loc.input.help.buildPlatform": "Geben Sie die Plattform an, die Sie erstellen möchten, z. B. Win32, x86, x64 oder Any CPU.", + "loc.input.label.buildForX86": "Build für x86", + "loc.input.help.buildForX86": "Erstellen Sie die Lösung für die x86-Plattform.", + "loc.input.label.buildForX64": "Build für x64", + "loc.input.help.buildForX64": "Erstellen Sie die Lösung für die x64-Plattform.", + "loc.input.label.buildForArm": "Build für ARM", + "loc.input.help.buildForArm": "Erstellen Sie die Lösung für die ARM-Plattform.", + "loc.input.label.buildForAnyCpu": "Build für Any CPU", + "loc.input.help.buildForAnyCpu": "Erstellen Sie die Lösung für die Any CPU-Plattform.", + "loc.input.label.updateAppVersion": "Aktualisieren der App-Version im Manifest", + "loc.input.help.updateAppVersion": "Aktualisieren Sie die App-Version von der im Manifest.", + "loc.input.label.manifestFile": "Manifestdatei zum Aktualisieren der Version", + "loc.input.help.manifestFile": "Pfad zur Manifestdatei. Diese wird nur verwendet, um die App-Version zu aktualisieren.", + "loc.input.label.appVersion": "App-Version", + "loc.input.help.appVersion": "Versionsnummer, die für die App festgelegt werden soll.", + "loc.input.label.appPackageDistributionMode": "Verteilungsmodus für Anwendungspakete", + "loc.input.help.appPackageDistributionMode": "Ob die .msixupload-Datei für den Store-Upload, die .msix/.msixbundle für Nicht-Store-Anwendungen oder beides generiert werden soll.", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "MSBuild-Version", + "loc.input.help.msbuildVersion": "Wenn die bevorzugte Version nicht gefunden werden kann, wird stattdessen die neueste Version verwendet.", + "loc.input.label.msbuildArchitecture": "MSBuild-Architektur", + "loc.input.help.msbuildArchitecture": "Stellen Sie optional die Architektur (x86, x64) von MSBuild zum Ausführen bereit.", + "loc.input.label.msbuildLocation": "Pfad zu MSBuild", + "loc.input.help.msbuildLocation": "Stellen Sie optional den Pfad zu MSBuild bereit." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/en-US/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/en-US/resources.resjson new file mode 100644 index 000000000..fe5a67c82 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/en-US/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "MSIX build and package", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Build and package Windows apps using the MSIX package format", + "loc.instanceNameFormat": "MSIX build and package", + "loc.input.label.outputPath": "Output Path", + "loc.input.help.outputPath": "Path of the generated package or bundle.", + "loc.input.label.buildSolution": "Build Solution with MSBuild", + "loc.input.help.buildSolution": "Invoke MSBuild and build the solution from scratch. If this is selected, you must provide a path to the solution file to build and select the target platforms to build.", + "loc.input.label.inputDirectory": "Input Directory", + "loc.input.help.inputDirectory": "Relative path from the repo root to the directory with the files to be packaged.", + "loc.input.label.solution": "Project to Build", + "loc.input.help.solution": "Relative path from repo root of the project or solution to build. Wildcards can be used.", + "loc.input.label.clean": "Clean Before Building", + "loc.input.help.clean": "Run a clean build (/t:clean) prior to the build.", + "loc.input.label.generateBundle": "Generate MSIX Bundle", + "loc.input.help.generateBundle": "Generate an MSIX bundle.", + "loc.input.label.buildConfiguration": "Configuration", + "loc.input.help.buildConfiguration": "The configuration to build the solution with. Only one configuration is supported at a time.", + "loc.input.label.buildPlatform": "Platform", + "loc.input.help.buildPlatform": "Specify the platform you want to build such as Win32, x86, x64 or any cpu.", + "loc.input.label.buildForX86": "Build for x86", + "loc.input.help.buildForX86": "Build the solution for the x86 platform.", + "loc.input.label.buildForX64": "Build for x64", + "loc.input.help.buildForX64": "Build the solution for the x64 platform.", + "loc.input.label.buildForArm": "Build for ARM", + "loc.input.help.buildForArm": "Build the solution for the ARM platform.", + "loc.input.label.buildForAnyCpu": "Build for Any CPU", + "loc.input.help.buildForAnyCpu": "Build the solution for the Any CPU platform.", + "loc.input.label.updateAppVersion": "Update App Version in Manifest", + "loc.input.help.updateAppVersion": "Update the app version from the one in the manifest.", + "loc.input.label.manifestFile": "Manifest File to Update Version", + "loc.input.help.manifestFile": "Path to the manifest file. This is only used to update the app version.", + "loc.input.label.appVersion": "App Version", + "loc.input.help.appVersion": "Version number to set for the app.", + "loc.input.label.appPackageDistributionMode": "Application Package Distribution Mode", + "loc.input.help.appPackageDistributionMode": "Whether to generate the .msixupload file for Store upload, the .msix/.msixbundle for non-Store apps, or both.", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "MSBuild Version", + "loc.input.help.msbuildVersion": "If the preferred version cannot be found, the latest version found will be used instead.", + "loc.input.label.msbuildArchitecture": "MSBuild Architecture", + "loc.input.help.msbuildArchitecture": "Optionally supply the architecture (x86, x64) of MSBuild to run.", + "loc.input.label.msbuildLocation": "Path to MSBuild", + "loc.input.help.msbuildLocation": "Optionally supply the path to MSBuild." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/es-ES/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/es-ES/resources.resjson new file mode 100644 index 000000000..51473c37b --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/es-ES/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "Compilación MSIX y paquete", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Compilar y empaquetar aplicaciones de Windows con el formato de paquete MSIX", + "loc.instanceNameFormat": "Compilación MSIX y paquete", + "loc.input.label.outputPath": "Ruta de salida", + "loc.input.help.outputPath": "La ruta de acceso del paquete o empaquetado generado.", + "loc.input.label.buildSolution": "Compilar una solución con MSBuild", + "loc.input.help.buildSolution": "Invoque MSBuild y genere la solución desde cero. Si se selecciona esta selección, debe proporcionar una ruta de acceso al archivo de solución para compilar y seleccionar las plataformas de destino que se van a crear.", + "loc.input.label.inputDirectory": "Directorio de entrada", + "loc.input.help.inputDirectory": "Ruta relativa de la raíz del repositorio al directorio con los archivos a empaquetar.", + "loc.input.label.solution": "Proyecto para crear", + "loc.input.help.solution": "Ruta de acceso relativa del repositorio raíz del proyecto o la solución que se va a compilar. Se pueden usar caracteres comodín.", + "loc.input.label.clean": "Limpiar antes de compilar", + "loc.input.help.clean": "Ejecute una compilación limpia (/t: Clean) antes de la compilación.", + "loc.input.label.generateBundle": "Generar un paquete MSIX", + "loc.input.help.generateBundle": "Generar un empaquetado MSIX", + "loc.input.label.buildConfiguration": "Configuración", + "loc.input.help.buildConfiguration": "Configuración con la que se va a generar la solución. Solo se admite una configuración a la vez.", + "loc.input.label.buildPlatform": "Plataforma", + "loc.input.help.buildPlatform": "Especifique la plataforma que quiere compilar, como Win32, x86, x64 o cualquier CPU.", + "loc.input.label.buildForX86": "Compilación para x86", + "loc.input.help.buildForX86": "Compile la solución para la plataforma x86.", + "loc.input.label.buildForX64": "Compilar para x64", + "loc.input.help.buildForX64": "Compile la solución para la plataforma x64.", + "loc.input.label.buildForArm": "Compilación para ARM", + "loc.input.help.buildForArm": "Compile la solución para la plataforma ARM.", + "loc.input.label.buildForAnyCpu": "Compilación para cualquier CPU", + "loc.input.help.buildForAnyCpu": "Compilar la solución para cualquier plataforma de CPU.", + "loc.input.label.updateAppVersion": "Actualice la versión de la aplicación en el manifiesto", + "loc.input.help.updateAppVersion": "Actualice la versión de la aplicación de la del manifiesto.", + "loc.input.label.manifestFile": "Archivo de manifiesto para actualizar la versión", + "loc.input.help.manifestFile": "Ruta de acceso al archivo de manifiesto. Solo se usa para actualizar la versión de la aplicación.", + "loc.input.label.appVersion": "Versión de la aplicación", + "loc.input.help.appVersion": "Número de versión a establecer para la aplicación.", + "loc.input.label.appPackageDistributionMode": "Modo de distribución de paquete de la aplicación", + "loc.input.help.appPackageDistributionMode": "Ya sea para generar el archivo .msixupload para cargarlo en la Store, el paquete .msix/.msixbundle para aplicaciones que no son de la Store, o ambos.", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "Versión de MSBuild", + "loc.input.help.msbuildVersion": "Si no se encuentra la versión preferida, se usará la última versión encontrada en su lugar.", + "loc.input.label.msbuildArchitecture": "Arquitectura de MSBuild", + "loc.input.help.msbuildArchitecture": "Si lo desea, puede proporcionar la arquitectura (x86, x64) de MSBuild para ejecutar.", + "loc.input.label.msbuildLocation": "Ruta de acceso a MSBuild", + "loc.input.help.msbuildLocation": "Si lo desea, puede proporcionar la ruta de acceso a MSBuild." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/fr-FR/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/fr-FR/resources.resjson new file mode 100644 index 000000000..1b6e2c0f8 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/fr-FR/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "Build et package MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Créer et empaqueter des applications Windows à l’aide du format de package MSIX", + "loc.instanceNameFormat": "Build et package MSIX", + "loc.input.label.outputPath": "Chemin d’accès de sortie", + "loc.input.help.outputPath": "Chemin d’accès du package ou du bundle groupé généré.", + "loc.input.label.buildSolution": "Créer une solution avec MSBuild", + "loc.input.help.buildSolution": "Appelez MSBuild et créez la solution de toutes pièces. Si vous sélectionnez cette option, vous devez fournir un chemin d’accès au fichier de solution pour générer et sélectionner les plateformes cibles à constituer.", + "loc.input.label.inputDirectory": "Répertoire d’entrée", + "loc.input.help.inputDirectory": "Chemin d’accès relatif de la racine du référentiel au répertoire contenant les fichiers à placer dans un package.", + "loc.input.label.solution": "Projet à créer", + "loc.input.help.solution": "Chemin d’accès de la racine du référentiel du projet ou de la solution à créer. Vous pouvez utiliser des caractères génériques.", + "loc.input.label.clean": "Nettoyer avant la création", + "loc.input.help.clean": "Exécutez une version propre (/t : Clean) avant la Build.", + "loc.input.label.generateBundle": "Générer un bundle MSIX", + "loc.input.help.generateBundle": "Générer un bundle MSIX.", + "loc.input.label.buildConfiguration": "Configuration", + "loc.input.help.buildConfiguration": "Configuration avec laquelle créer la solution. Une seule configuration est prise en charge à la fois.", + "loc.input.label.buildPlatform": "Plateforme", + "loc.input.help.buildPlatform": "Indiquez la plateforme que vous voulez créer, par exemple, Win32, x86, x64 ou tout processeur.", + "loc.input.label.buildForX86": "Build pour x86", + "loc.input.help.buildForX86": "Créez la solution pour la plateforme x86.", + "loc.input.label.buildForX64": "Build pour x64", + "loc.input.help.buildForX64": "Créez la solution pour la plateforme x64.", + "loc.input.label.buildForArm": "Build pour ARM", + "loc.input.help.buildForArm": "Créez la solution pour la plateforme ARM.", + "loc.input.label.buildForAnyCpu": "Build pour toute unité centrale", + "loc.input.help.buildForAnyCpu": "Créez la solution pour la plateforme Any CPU.", + "loc.input.label.updateAppVersion": "Mise à jour de la version de l’application dans Manifest", + "loc.input.help.updateAppVersion": "Mettez à jour la version de l’application à partir de celle du manifeste.", + "loc.input.label.manifestFile": "Fichier Manifest pour la version de mise à jour", + "loc.input.help.manifestFile": "Chemin d’accès au fichier manifeste. Utilisé uniquement pour mettre à jour la version de l’application.", + "loc.input.label.appVersion": "Version de l’application", + "loc.input.help.appVersion": "Numéro de version à définir pour l’application.", + "loc.input.label.appPackageDistributionMode": "Mode de distribution du package d’application", + "loc.input.help.appPackageDistributionMode": "Que ce soit pour générer le fichier .msixupload pour le chargement du Store, le .msix/.msixbundle pour les applications non-Store, ou les deux.", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "Version de MSBuild", + "loc.input.help.msbuildVersion": "Si la version par défaut est introuvable, la dernière version trouvée sera utilisée à la place.", + "loc.input.label.msbuildArchitecture": "Architecture MSBuild", + "loc.input.help.msbuildArchitecture": "Vous pouvez éventuellement fournir l’architecture (x86, x64) de MSBuild pour l’exécuter.", + "loc.input.label.msbuildLocation": "Chemin d’accès de MSBuild", + "loc.input.help.msbuildLocation": "Fournissez éventuellement le chemin d’accès de MSBuild." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/it-IT/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/it-IT/resources.resjson new file mode 100644 index 000000000..bf41e8077 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/it-IT/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "Build e pacchetto MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Compila e impacchetta app di Windows usando il formato MSIX del pacchetto", + "loc.instanceNameFormat": "Build e pacchetto MSIX", + "loc.input.label.outputPath": "Percorso di output", + "loc.input.help.outputPath": "Percorso del pacchetto o bundle generato.", + "loc.input.label.buildSolution": "Compila soluzione con MSBuild", + "loc.input.help.buildSolution": "Invocare MSBuild e compilare la soluzione da zero. Se viene selezionato, bisogna inserire un percorso del file della soluzione da compilare e selezionare la piattaforme di destinazione da compilare.", + "loc.input.label.inputDirectory": "Directory di input", + "loc.input.help.inputDirectory": "Percorso relativo della radice che viene remodificato nella directory con i file da effettuare in un pacchetto.", + "loc.input.label.solution": "Progetto da compilare", + "loc.input.help.solution": "Percorso relativo della radice del progetto o della soluzione da creare. È possibile utilizzare i caratteri jolly.", + "loc.input.label.clean": "Pulisci prima di compilare", + "loc.input.help.clean": "Esegui una build pulita (/t:clean) prima della build.", + "loc.input.label.generateBundle": "Generare il bundle di MSIX", + "loc.input.help.generateBundle": "Generare un bundle di MSIX.", + "loc.input.label.buildConfiguration": "Configurazione", + "loc.input.help.buildConfiguration": "Configurazione per la compilazione della soluzione. È supportata una sola configurazione alla volta.", + "loc.input.label.buildPlatform": "Piattaforma", + "loc.input.help.buildPlatform": "Specifica la piattaforma che vuoi compilare, come Win32, x86, x64 o qualsiasi CPU.", + "loc.input.label.buildForX86": "Build per x86", + "loc.input.help.buildForX86": "Compila la soluzione per piattaforma x86.", + "loc.input.label.buildForX64": "Build per x64", + "loc.input.help.buildForX64": "Compila la soluzione per piattaforma x64.", + "loc.input.label.buildForArm": "Build per ARM", + "loc.input.help.buildForArm": "Compila la soluzione per piattaforma ARM.", + "loc.input.label.buildForAnyCpu": "Compila per Any CPU", + "loc.input.help.buildForAnyCpu": "Compila la soluzione per piattaforma Any CPU.", + "loc.input.label.updateAppVersion": "Aggiorna versione app nel manifesto", + "loc.input.help.updateAppVersion": "Aggiorna la versione dell'app da quella nel manifesto.", + "loc.input.label.manifestFile": "File manifesto per versione di aggiornamento", + "loc.input.help.manifestFile": "Percorso del file manifesto. Questa procedura viene usata solo per aggiornare la versione dell'app.", + "loc.input.label.appVersion": "Versione app", + "loc.input.help.appVersion": "Numero versione da configurare per l'app.", + "loc.input.label.appPackageDistributionMode": "Modalità di distribuzione del pacchetto dell'applicazione", + "loc.input.help.appPackageDistributionMode": "Se generare il file con estensione msixupload per il caricamento in archivio, il file con estensione msix/msixbundle per le app non archiviate o entrambi.", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "Versione MSBuild", + "loc.input.help.msbuildVersion": "Se non è possibile trovare la versione preferita, verrà usata l'ultima versione trovata.", + "loc.input.label.msbuildArchitecture": "Architettura MSBuild", + "loc.input.help.msbuildArchitecture": "Fornire facoltativamente l'architettura (x86, x64) di MSBuild da eseguire.", + "loc.input.label.msbuildLocation": "Percorso di MSBuild", + "loc.input.help.msbuildLocation": "È possibile specificare facoltativamente il percorso di MSBuild." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/ja-JP/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/ja-JP/resources.resjson new file mode 100644 index 000000000..26fe8dc97 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/ja-JP/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "MSIX ビルドとパッケージ", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "MSIX パッケージ形式を使用して Windows アプリのビルドとパッケージ化を行う", + "loc.instanceNameFormat": "MSIX ビルドとパッケージ", + "loc.input.label.outputPath": "出力パス", + "loc.input.help.outputPath": "生成されたパッケージまたはバンドルのパスです。", + "loc.input.label.buildSolution": "MSBuild を使用したビルド ソリューション", + "loc.input.help.buildSolution": "MSBuild を呼び出して、ソリューションを最初からビルドします。 これを選択した場合は、ビルドするソリューションファイルへのパスを指定し、ビルドするターゲットプラットフォームを選択する必要があります。", + "loc.input.label.inputDirectory": "入力ディレクトリ", + "loc.input.help.inputDirectory": "リポジトリのルートから、パッケージするファイルのディレクトリまでの相対パスです。", + "loc.input.label.solution": "ビルドするプロジェクト", + "loc.input.help.solution": "ビルドするプロジェクトまたはソリューションのリポジトリ ルートからの相対パスです。ワイルドカードを使用できます。", + "loc.input.label.clean": "ビルド前にクリーニングする", + "loc.input.help.clean": "ビルドを行う前にクリーン ビルド (/t:clean) を実行します。", + "loc.input.label.generateBundle": "MSIX バンドルの生成", + "loc.input.help.generateBundle": "MSIX バンドルを生成します。", + "loc.input.label.buildConfiguration": "構成", + "loc.input.help.buildConfiguration": "ソリューションをビルドする構成です。一度にサポートされる構成は1つだけです。", + "loc.input.label.buildPlatform": "プラットフォーム", + "loc.input.help.buildPlatform": "Win32、x86、x64、または Any CPU など、ビルドするプラットフォームを指定します。", + "loc.input.label.buildForX86": "x86 用のビルド", + "loc.input.help.buildForX86": "x86 プラットフォーム用のソリューションをビルドします。", + "loc.input.label.buildForX64": "x64 用のビルド", + "loc.input.help.buildForX64": "x64 プラットフォーム用のソリューションをビルドします。", + "loc.input.label.buildForArm": "ARM 用のビルド", + "loc.input.help.buildForArm": "ARM プラットフォーム用のソリューションをビルドします。", + "loc.input.label.buildForAnyCpu": "Any CPU 用のビルド", + "loc.input.help.buildForAnyCpu": "Any CPU プラットフォーム用のソリューションをビルドします。", + "loc.input.label.updateAppVersion": "マニフェストでアプリ バージョンを更新する", + "loc.input.help.updateAppVersion": "マニフェスト内のアプリバージョンを更新します。", + "loc.input.label.manifestFile": "バージョンを更新するマニフェスト ファイル", + "loc.input.help.manifestFile": "マニフェストファイルへのパス。 これは、アプリのバージョンを更新するためだけに使用されます。", + "loc.input.label.appVersion": "アプリのバージョン", + "loc.input.help.appVersion": "アプリに設定するバージョン番号です。", + "loc.input.label.appPackageDistributionMode": "アプリケーション パッケージ配布モード", + "loc.input.help.appPackageDistributionMode": "Store へのアップロード用に .msixupload ファイルを生成するか、Store 以外のアプリ用に .msix/.msixbundle を生成するか、それとも両方とも生成するか。", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "MSBuild バージョン", + "loc.input.help.msbuildVersion": "優先するバージョンが見つからない場合は、見つかった最新バージョンが代わりに使用されます。", + "loc.input.label.msbuildArchitecture": "MSBuild アーキテクチャ", + "loc.input.help.msbuildArchitecture": "必要に応じて、実行する MSBuild のアーキテクチャ (x86、x64) を指定します。", + "loc.input.label.msbuildLocation": "MSBuild へのパス", + "loc.input.help.msbuildLocation": "必要に応じて MSBuild へのパスを指定します。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/ko-KR/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/ko-KR/resources.resjson new file mode 100644 index 000000000..4728009b6 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/ko-KR/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "MSIX 빌드 및 패키지", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "MSIX 패키지 형식을 사용하여 Windows 앱 빌드 및 패키지", + "loc.instanceNameFormat": "MSIX 빌드 및 패키지", + "loc.input.label.outputPath": "출력 경로", + "loc.input.help.outputPath": "생성 된 패키지 또는 번들의 경로입니다.", + "loc.input.label.buildSolution": "MSBuild를 사용하여 솔루션 빌드", + "loc.input.help.buildSolution": "MSBuild를 호출하고 솔루션을 처음부터 빌드합니다. 이 옵션을 선택한 경우 빌드를 위해 솔루션 파일에 대한 경로를 제공하고 빌드할 대상 플랫폼을 선택해야합니다.", + "loc.input.label.inputDirectory": "입력 디렉토리", + "loc.input.help.inputDirectory": "파일을 포함하는 디렉터리로 저장소 루트경로를 정리할 수 있습니다.", + "loc.input.label.solution": "빌드할 프로젝트", + "loc.input.help.solution": "프로젝트 또는 솔루션의 리포지셔링에서 검존하는 상대 경로입니다. 와일드카드를 사용할 수 있습니다.", + "loc.input.label.clean": "구성 전 정리", + "loc.input.help.clean": "빌드 전에 정리 빌드(/t:clean)를 실행합니다.", + "loc.input.label.generateBundle": "MSIX 번들 생성", + "loc.input.help.generateBundle": "MSIX 번들을 생성합니다.", + "loc.input.label.buildConfiguration": "구성", + "loc.input.help.buildConfiguration": "솔루션을 만드는 구성입니다. 한 번에 하나의 구성만 지원됩니다.", + "loc.input.label.buildPlatform": "플랫폼", + "loc.input.help.buildPlatform": "Win32, x86, x64 또는 CPU와 같이 빌드하려는 플랫폼을 지정하세요.", + "loc.input.label.buildForX86": "x86용 빌드", + "loc.input.help.buildForX86": "x86 플랫폼용 솔루션을 구축합니다.", + "loc.input.label.buildForX64": "x64용 빌드", + "loc.input.help.buildForX64": "x64 플랫폼용 솔루션을 구축합니다.", + "loc.input.label.buildForArm": "ARM용 빌드", + "loc.input.help.buildForArm": "ARM 플랫폼용 솔루션을 구축합니다.", + "loc.input.label.buildForAnyCpu": "모든 CPU용 빌드", + "loc.input.help.buildForAnyCpu": "모든 CPU 플랫폼에 대한 솔루션을 작성합니다.", + "loc.input.label.updateAppVersion": "매니페스트의 앱 버전 업데이트", + "loc.input.help.updateAppVersion": "매니페스트에 있는 앱 버전에서 앱 버전을 업데이트합니다.", + "loc.input.label.manifestFile": "버전 업데이트를 위한 매니페스트 파일", + "loc.input.help.manifestFile": "매니페스트 파일의 경로입니다. 앱 버전을 업데이트하는 데만 사용됩니다.", + "loc.input.label.appVersion": "앱 버전", + "loc.input.help.appVersion": "앱에 설정할 버전 번호입니다.", + "loc.input.label.appPackageDistributionMode": "응용 프로그램 패키지 배포 모드", + "loc.input.help.appPackageDistributionMode": "Store 업로드를 위한 .msixupload 파일을 생성할지, 비 Store 앱의 경우 .m6/.m6bundle을 생성할지 아니면 두 가지 모두를 생성할지 여부를 지정합니다.", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "MSBuild 버전", + "loc.input.help.msbuildVersion": "기본 버전을 찾을 수 없으면 검색된 최신 버전이 대신 사용됩니다.", + "loc.input.label.msbuildArchitecture": "MSBuild 아키텍처", + "loc.input.help.msbuildArchitecture": "실행할 MSBuild의 아키텍처(x86, x64)를 지정합니다.", + "loc.input.label.msbuildLocation": "MSBuild 경로", + "loc.input.help.msbuildLocation": "선택적으로 MSBuild에 대한 경로를 제공합니다." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/pt-BR/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/pt-BR/resources.resjson new file mode 100644 index 000000000..e41bc11de --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/pt-BR/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "Compilação e pacote MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Compilar e empacotar aplicativos do Windows usando o formato de pacote MSIX", + "loc.instanceNameFormat": "Compilação e pacote MSIX", + "loc.input.label.outputPath": "Caminho de Saída", + "loc.input.help.outputPath": "Caminho do pacote ou lote gerado.", + "loc.input.label.buildSolution": "Compilar Solução com MSBuild", + "loc.input.help.buildSolution": "Invoque o MSBuild e crie a solução a partir do zero. Se estiver selecionada, você deve fornecer um caminho para o arquivo de solução para criar e selecionar as plataformas de destino a serem criadas.", + "loc.input.label.inputDirectory": "Diretório de Entrada", + "loc.input.help.inputDirectory": "Caminho relativo da raiz do repositório para o diretório com os arquivos a serem empacotados.", + "loc.input.label.solution": "Projeto a ser Compilado", + "loc.input.help.solution": "Caminho relativo da raiz de repositório do projeto ou solução a ser compilado. Caracteres curinga podem ser usados.", + "loc.input.label.clean": "Limpar Antes de Compilar", + "loc.input.help.clean": "Execute uma compilação limpa (/t: Clean) antes da compilação.", + "loc.input.label.generateBundle": "Gerar lote MSIX", + "loc.input.help.generateBundle": "Gerar um lote MSIX.", + "loc.input.label.buildConfiguration": "Configuração", + "loc.input.help.buildConfiguration": "A configuração com a qual criar a solução. Há suporte para apenas uma configuração por vez.", + "loc.input.label.buildPlatform": "Plataforma", + "loc.input.help.buildPlatform": "Especifique a plataforma que você deseja construir como Win32, x86, x64 ou qualquer CPU.", + "loc.input.label.buildForX86": "Compilar com x86", + "loc.input.help.buildForX86": "Compile a solução para a plataforma x86.", + "loc.input.label.buildForX64": "Compilar com x64", + "loc.input.help.buildForX64": "Compile a solução para a plataforma x64.", + "loc.input.label.buildForArm": "Compilar com ARM", + "loc.input.help.buildForArm": "Compile a solução para a plataforma ARM.", + "loc.input.label.buildForAnyCpu": "Compilar para Qualquer CPU", + "loc.input.help.buildForAnyCpu": "Crie a solução para a plataforma de qualquer CPU.", + "loc.input.label.updateAppVersion": "Atualizar a Versão do Aplicativo no Manifesto", + "loc.input.help.updateAppVersion": "Atualize a versão do aplicativo a partir daquela no manifesto.", + "loc.input.label.manifestFile": "Arquivo de Manifesto para Atualizar Versão", + "loc.input.help.manifestFile": "Caminho para o arquivo de manifesto. Isso é usado apenas para atualizar a versão do aplicativo.", + "loc.input.label.appVersion": "Versão do Aplicativo", + "loc.input.help.appVersion": "Número de versão a definir para o aplicativo.", + "loc.input.label.appPackageDistributionMode": "Modo de Distribuição de Pacote de Aplicativo", + "loc.input.help.appPackageDistributionMode": "Gerar o arquivo .msixupload para carregar para o Microsoft Store, o .msix/.msixbundle para aplicativos que não são do Microsoft Store ou ambos.", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "Versão de MSBuild", + "loc.input.help.msbuildVersion": "Se a versão preferencial não puder ser encontrada, a versão mais recente encontrada será usada.", + "loc.input.label.msbuildArchitecture": "Arquitetura MSBuild", + "loc.input.help.msbuildArchitecture": "Opcionalmente, forneça a arquitetura (x86, x64) do MSBuild a ser executada.", + "loc.input.label.msbuildLocation": "Caminho para o MSBuild", + "loc.input.help.msbuildLocation": "Forneça o caminho para o MSBuild." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/ru-RU/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/ru-RU/resources.resjson new file mode 100644 index 000000000..f129de59a --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/ru-RU/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "Сборка и пакет MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Собирайте и упаковывайте приложения Windows с помощью формата пакетов MSIX", + "loc.instanceNameFormat": "Сборка и пакет MSIX", + "loc.input.label.outputPath": "Путь вывода", + "loc.input.help.outputPath": "Путь к созданному пакету или набору.", + "loc.input.label.buildSolution": "Создание решения с помощью MSBuild", + "loc.input.help.buildSolution": "Вызовите MSBuild и соберите решение с нуля. При выборе данного пункта следует указать путь к файлу решения, чтобы выбрать платформу для сборки.", + "loc.input.label.inputDirectory": "Каталог ввода", + "loc.input.help.inputDirectory": "Относительный путь от корня репозитория до каталога с файлами для пакета.", + "loc.input.label.solution": "Проект для сборки", + "loc.input.help.solution": "Относительный путь к корню репозитория проекта или решения для сборки. Можно использовать подстановочные знаки.", + "loc.input.label.clean": "Очистка перед началом сборкой", + "loc.input.help.clean": "Запустите чистую сборку (/t:clean) до сборки.", + "loc.input.label.generateBundle": "Создать набор MSIX", + "loc.input.help.generateBundle": "Создайте набор MSIX.", + "loc.input.label.buildConfiguration": "Конфигурация", + "loc.input.help.buildConfiguration": "Конфигурация, с которой нужно создать решение. Поддерживается только одна конфигурация.", + "loc.input.label.buildPlatform": "Платформа", + "loc.input.help.buildPlatform": "Выберите платформу, которую вы хотите собрать: Win32, x86, x64 или любой процессор.", + "loc.input.label.buildForX86": "Сборка для x86", + "loc.input.help.buildForX86": "Создайте решение для платформы x86.", + "loc.input.label.buildForX64": "Сборка для x64", + "loc.input.help.buildForX64": "Создайте решение для платформы x64.", + "loc.input.label.buildForArm": "Сборка для ARM", + "loc.input.help.buildForArm": "Создайте решение для любой платформы ARM.", + "loc.input.label.buildForAnyCpu": "Сборка для любого процессора", + "loc.input.help.buildForAnyCpu": "Создайте решение для любой процессорной платформы.", + "loc.input.label.updateAppVersion": "Обновление версии приложения в манифесте", + "loc.input.help.updateAppVersion": "Обновление версии приложения из в манифесте.", + "loc.input.label.manifestFile": "Файл манифеста для обновления версии", + "loc.input.help.manifestFile": "Путь к файлу манифеста. Используется только для обновления версии приложения.", + "loc.input.label.appVersion": "Версия приложения", + "loc.input.help.appVersion": "Номер версии, который нужно настроить для приложения.", + "loc.input.label.appPackageDistributionMode": "Режим распространения пакета приложения", + "loc.input.help.appPackageDistributionMode": "Создать файл .msixupload для отправки в Store, файл .msix/.msixbundle для приложений не из Store или оба файла.", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "Версия MSBuild", + "loc.input.help.msbuildVersion": "Если не удается найти нужную версию, вместо нее будет использоваться последняя найденная версия.", + "loc.input.label.msbuildArchitecture": "Архитектура MSBuild", + "loc.input.help.msbuildArchitecture": "При необходимости укажите архитектуру (x86, x64) MSBuild для выполнения.", + "loc.input.label.msbuildLocation": "Путь к MSBuild", + "loc.input.help.msbuildLocation": "Дополнительно можно указать путь к MSBuild." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/zh-CN/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/zh-CN/resources.resjson new file mode 100644 index 000000000..e2829093b --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/zh-CN/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "MSIX 构建和程序包", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "使用 MSIX 程序包格式构建和打包 Windows 应用", + "loc.instanceNameFormat": "MSIX 构建和程序包", + "loc.input.label.outputPath": "输出路径", + "loc.input.help.outputPath": "所生成程序包或捆绑包的路径。", + "loc.input.label.buildSolution": "使用 MSBuild 生成解决方案", + "loc.input.help.buildSolution": "从头开始调用 MSBuild 并生成解决方案。 如果选中此选项,则必须提供解决方案文件的路径,以便生成并选择要生成的目标平台。", + "loc.input.label.inputDirectory": "输入目录", + "loc.input.help.inputDirectory": "从库根到要打包的文件的相对路径。", + "loc.input.label.solution": "要生成的项目", + "loc.input.help.solution": "要生成的项目或解决方案的库根的相对路径。 可以使用通配符。", + "loc.input.label.clean": "构建前清除", + "loc.input.help.clean": "在构建之前运行干净的构建 (/t:clean)。", + "loc.input.label.generateBundle": "生成 MSIX 捆绑包", + "loc.input.help.generateBundle": "生成 MSIX 捆绑包。", + "loc.input.label.buildConfiguration": "配置", + "loc.input.help.buildConfiguration": "用来生成解决方案的配置。一次仅支持一个配置。", + "loc.input.label.buildPlatform": "平台", + "loc.input.help.buildPlatform": "指定要构建的平台(如 Win32、x86、x64 或任何 cpu)。", + "loc.input.label.buildForX86": "构建 x86 版", + "loc.input.help.buildForX86": "构建适用于 x86 平台的解决方案。", + "loc.input.label.buildForX64": "构建 x64", + "loc.input.help.buildForX64": "构建适用于 x64 平台的解决方案。", + "loc.input.label.buildForArm": "面向 ARM 的内部版本", + "loc.input.help.buildForArm": "构建 ARM 平台的解决方案。", + "loc.input.label.buildForAnyCpu": "为任何 CPU 构建", + "loc.input.help.buildForAnyCpu": "为任何 CPU 平台构建解决方案。", + "loc.input.label.updateAppVersion": "更新清单中的应用版本", + "loc.input.help.updateAppVersion": "从清单中的应用程序版本更新。", + "loc.input.label.manifestFile": "用于更新版本的清单文件", + "loc.input.help.manifestFile": "清单文件的路径。 此操作仅用于更新应用版本。", + "loc.input.label.appVersion": "应用版本", + "loc.input.help.appVersion": "要为应用设置的版本号。", + "loc.input.label.appPackageDistributionMode": "应用程序包分发模式", + "loc.input.help.appPackageDistributionMode": "是生成用于 Microsoft Store 上传的 .msixupload 文件,还是生成用于非 Microsoft Store 应用程序的 .msix/.msixbundle,还是同时生成两者。", + "loc.input.label.msbuildLocationMethod": "Msbuild", + "loc.input.label.msbuildVersion": "MSBuild 版本", + "loc.input.help.msbuildVersion": "如果找不到首选版本,将改用找到的最新版本。", + "loc.input.label.msbuildArchitecture": "MSBuild 体系结构", + "loc.input.help.msbuildArchitecture": "选择性提供要运行的 MSBuild 的体系结构(x86、x64)。", + "loc.input.label.msbuildLocation": "MSBuild 路径", + "loc.input.help.msbuildLocation": "选择性地提供指向 MSBuild 的路径。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/zh-TW/resources.resjson b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/zh-TW/resources.resjson new file mode 100644 index 000000000..1c273508e --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/Strings/resources.resjson/zh-TW/resources.resjson @@ -0,0 +1,45 @@ +{ + "loc.friendlyName": "MSIX 組建與套件", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "使用 MSIX 套件格式建置並封裝 Windows 應用程式", + "loc.instanceNameFormat": "MSIX 組建與套件", + "loc.input.label.outputPath": "輸出路徑", + "loc.input.help.outputPath": "所產生套件或套件組合的路徑。", + "loc.input.label.buildSolution": "使用 MSBuild 建置方案", + "loc.input.help.buildSolution": "叫用 MSBuild 並從頭建立解決方案。如果選取此選項,您必須提供方案檔的路徑來建置,並選取要建置的目標平台。", + "loc.input.label.inputDirectory": "輸入目錄", + "loc.input.help.inputDirectory": "從儲存機制根路徑到欲封裝檔案的相對路徑。", + "loc.input.label.solution": "要建置的專案", + "loc.input.help.solution": "從儲存機制根路徑建置的專案或方案之相對路徑。可以使用萬用字元。", + "loc.input.label.clean": "在建置前清除", + "loc.input.help.clean": "在建置之前執行清除建置 (/t:clean)。", + "loc.input.label.generateBundle": "產生 MSIX 套件組合", + "loc.input.help.generateBundle": "產生 MSIX 套件組合。", + "loc.input.label.buildConfiguration": "設定", + "loc.input.help.buildConfiguration": "建立解決方案的設定。一次只支援一個設定。", + "loc.input.label.buildPlatform": "平台", + "loc.input.help.buildPlatform": "指定您想要建置的平台,例如 Win32、x86、x64 或任何 CPU。", + "loc.input.label.buildForX86": "x86 的組建", + "loc.input.help.buildForX86": "為 x86 平台建置方案。", + "loc.input.label.buildForX64": "x64 的組建", + "loc.input.help.buildForX64": "為 x64 平台建置方案。", + "loc.input.label.buildForArm": "ARM 的組建", + "loc.input.help.buildForArm": "為 ARM 平台建置方案。", + "loc.input.label.buildForAnyCpu": "適用於任何 CPU 的組建", + "loc.input.help.buildForAnyCpu": "為任何 CPU 平台建置方案。", + "loc.input.label.updateAppVersion": "更新資訊清單中的應用程式版本", + "loc.input.help.updateAppVersion": "從資訊清單中的應用程式版本更新。", + "loc.input.label.manifestFile": "要更新版本的資訊清單檔案", + "loc.input.help.manifestFile": "資訊清單檔案的路徑。 這只是用來更新應用程式版本。", + "loc.input.label.appVersion": "應用程式版本", + "loc.input.help.appVersion": "要為應用程式設定的版本號碼。", + "loc.input.label.appPackageDistributionMode": "應用程式套件發佈模式", + "loc.input.help.appPackageDistributionMode": "產生用於市集上傳的 msixupload 檔案、用於非市集應用程式的 msix/msixbundle,或兩者皆是。", + "loc.input.label.msbuildLocationMethod": "MSBuild", + "loc.input.label.msbuildVersion": "MSBuild 版本", + "loc.input.help.msbuildVersion": "如果找不到慣用的版本,則會改為使用所找到的最新版本。", + "loc.input.label.msbuildArchitecture": "MSBuild 架構", + "loc.input.help.msbuildArchitecture": "選擇性提供要執行的 MSBuild 架構 (x86、x64)。", + "loc.input.label.msbuildLocation": "MSBuild 的路徑", + "loc.input.help.msbuildLocation": "選擇性提供 MSBuild 的路徑。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/debug_inputs.env b/tools/pipelines-tasks/MsixPackaging/debug_inputs.env new file mode 100644 index 000000000..f80d4a6fd --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/debug_inputs.env @@ -0,0 +1,28 @@ +# Set the task's inputs here to use when debugging + +# This is to allow loading the VSTS SDK for the MSBuild helpers +SYSTEM_CULTURE="en-US" + +INPUT_BUILDSOLUTION=false +INPUT_CLEAN=true + +INPUT_OUTPUTPATH=debug\App.msix + +INPUT_INPUTDIRECTORY=test\assets\PrebuiltBinaries\ + +INPUT_SOLUTION=test\assets\HelloWorldUWPApp\HelloWorldApp.sln +INPUT_BUILDCONFIGURATION=debug +INPUT_BUILDPLATFORM=x86 +INPUT_BUILDFORX86=true +INPUT_BUILDFORX64=true + +INPUT_GENERATEBUNDLE=false + +INPUT_MSBUILDLOCATIONMETHOD=version +INPUT_MSBUILDVERSION=latest +INPUT_MSBUILDARCHITECTURE=x86 +INPUT_MSBUILDLOCATION=C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\MSBuild\Current\Bin\msbuild.exe + +INPUT_UPDATEAPPVERSION=true +INPUT_MANIFESTFILE=test\assets\HelloWorldUWPApp\Package.appxmanifest +INPUT_APPVERSION=1.2.3.4 diff --git a/tools/pipelines-tasks/MsixPackaging/icon.png b/tools/pipelines-tasks/MsixPackaging/icon.png new file mode 100644 index 000000000..052291f00 Binary files /dev/null and b/tools/pipelines-tasks/MsixPackaging/icon.png differ diff --git a/tools/pipelines-tasks/MsixPackaging/index.ts b/tools/pipelines-tasks/MsixPackaging/index.ts new file mode 100644 index 000000000..d47c207db --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/index.ts @@ -0,0 +1,161 @@ +import path = require('path'); +import tl = require('azure-pipelines-task-lib/task') +import { ToolRunner } from 'azure-pipelines-task-lib/toolrunner'; + +import helpers = require('common/helpers'); + +import msbuild = require('./msbuild'); + +/** + * Reads the inputs needed for updating the app version in the manifest, + * then edits it with the new version. + */ +const updateAppVersionInManifest = async () => +{ + // read the input + const manifestFile: string = helpers.getInputWithErrorCheck('manifestFile', 'To update the version of the app, the path to the manifest file is required to get the current version, but none was given.'); + + // get the new version + let newVersion: string = helpers.getInputWithErrorCheck('appVersion', 'To manually update the version of the app, a new version is required but none was given.'); + + const parsedManifest: any = await helpers.parseXml(manifestFile); + parsedManifest.Package.Identity[0].$.Version = newVersion; + helpers.writeXml(parsedManifest, manifestFile); +} + +/** + * Gets the list of platforms to build from the input. + */ +const getPlatformsToBuild = (): string[] => +{ + const allPlatforms: { [platform: string]: boolean } = + { + 'x86': tl.getBoolInput('buildForX86'), + 'x64': tl.getBoolInput('buildForX64'), + 'ARM': tl.getBoolInput('buildForArm'), + 'Any CPU': tl.getBoolInput('buildForAnyCpu') + }; + + const selectedPlatforms: string[] = []; + Object.keys(allPlatforms).forEach(key => + { + if (allPlatforms[key]) + { + selectedPlatforms.push(key); + } + }); + + return selectedPlatforms; +} + +/** + * Reads the inputs for MSBuild and calls it to build the solution and + * create the packages and bundle. + * @param outputPath Save location for the produced package or bundle. + * @param createBundle Whether we are creating a bundle or a package. + */ +const setUpAndRunMSBuild = async (outputPath: string, createBundle: boolean) => +{ + const msbuildCommonParameters: msbuild.MSBuildCommonParameters = msbuild.readMSBuildInputs(); + + if (createBundle) + { + const platformsToBuild: string[] = getPlatformsToBuild(); + if (!platformsToBuild.length) + { + throw Error('No platform was specified to be built.'); + } + + await msbuild.runMSBuild(createBundle, outputPath, platformsToBuild, msbuildCommonParameters); + } + else + { + const platform: string = helpers.getInputWithErrorCheck('buildPlatform', 'Platform to build is required.'); + await msbuild.runMSBuild(createBundle, outputPath, [platform], msbuildCommonParameters); + } +} + +/** + * Package prebuilt binaries. + * @param outputPath Save location for the generated package or bundle. + * @param createBundle Whether to produce a bundle or a package. + */ +const packageBuiltBinaries = async (outputPath: string, createBundle: boolean, inputDirectory: string) => +{ + // The makemsix tool has a quirk where if the path ends with a slash, the tool + // will fail, so we detect this and work around this problem by removing the + // trailing slash if present + const lastChar: string = inputDirectory[inputDirectory.length - 1]; + if (lastChar === '\\' || lastChar === '/') + { + console.log("inputDirectory ends with \\ trimming it..."); + inputDirectory = inputDirectory.slice(0, -1); + } + + if (!createBundle) + { + // TODO: Replace makeappx by makemsix to make it cross-platform + const makeAppxPackRunner: ToolRunner = tl.tool(helpers.MAKEAPPX_PATH); + makeAppxPackRunner.arg('pack'); + makeAppxPackRunner.arg(['/o', '/v']); + makeAppxPackRunner.arg(['/d', inputDirectory]); + makeAppxPackRunner.arg(['/p', outputPath]); + await makeAppxPackRunner.exec(); + } + else + { + const makeAppxBundleRunner: ToolRunner = tl.tool(helpers.MAKEAPPX_PATH); + makeAppxBundleRunner.arg('bundle'); + makeAppxBundleRunner.arg(['/o', '/v']); + makeAppxBundleRunner.arg(['/d', inputDirectory]); + makeAppxBundleRunner.arg(['/p', outputPath]); + await makeAppxBundleRunner.exec(); + } +} + +/** + * Main function for the task. + */ +const run = async () => +{ + tl.setResourcePath(path.join(__dirname, 'task.json')); + + // detect if the user will provide pre-built binaries or use MSBuild + const buildSolution: boolean = tl.getBoolInput('buildSolution', /* required: */ true); + let inputDirectory: string | undefined; + if (!buildSolution) + { + inputDirectory = helpers.getInputWithErrorCheck('inputDirectory', 'To package pre-built binaries, a path to the directory containing valid binaries is required, but none was given.'); + } + + // read output path for the package or bundle. + // resolve it to a full path to ensure it is the same in every place. + // e.g. MSBuild seems to use the path relative to the solution dir in some cases. + const outputPath: string = path.resolve(helpers.getInputWithErrorCheck('outputPath', 'An output path is required to save the package, but none was given.')); + + // whether to bundle or not is independent of whether or not to build from scratch + // if the user gives the bundle option, check that they gave a path to save the output bundle. + const generateBundle: boolean = tl.getBoolInput('generateBundle'); + + // update the app version in the manifest + const updateAppVersion: boolean = tl.getBoolInput('updateAppVersion'); + if (updateAppVersion) + { + await updateAppVersionInManifest(); + } + + // create the packages and bundle + if (buildSolution) + { + await setUpAndRunMSBuild(outputPath, generateBundle); + } + else + { + await packageBuiltBinaries(outputPath, generateBundle, inputDirectory!); + } +} + +run().catch(err => + { + tl.setResult(tl.TaskResult.Failed, err.message); + }) \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/msbuild.ts b/tools/pipelines-tasks/MsixPackaging/msbuild.ts new file mode 100644 index 000000000..cc777f371 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/msbuild.ts @@ -0,0 +1,162 @@ +import os = require('os'); +import path = require('path'); +import tl = require('azure-pipelines-task-lib/task'); +import { ToolRunner } from 'azure-pipelines-task-lib/toolrunner'; + +import helpers = require('common/helpers'); + +import msbuildHelpers = require('./MSBuildHelpers/msbuildhelpers'); + +const MSBUILD_PATH_HELPER_SCRIPT = path.join(__dirname, 'GetMSBuildPath.ps1'); + +export interface MSBuildCommonParameters +{ + solution: string; + configuration: string; + msbuildArguments: string; + appxPackageBuildMode: string, + clean: boolean; + msbuildLocationMethod: string; + msbuildLocation?: string; + msbuildVersion?: string; + msbuildArchitecture?: string; +} + +/** + * Reads the task's inputs related to MSBuild that are common to creating packages and bundle. + */ +export const readMSBuildInputs = (): MSBuildCommonParameters => +{ + const solution: string = helpers.getInputWithErrorCheck('solution', 'A path to a solution is required.'); + const configuration: string = helpers.getInputWithErrorCheck('buildConfiguration', 'Build configuration is required.'); + const msbuildArguments: string = '/p:AppxPackageSigningEnabled=false'; + const clean: boolean = tl.getBoolInput('clean'); + const appxPackageBuildMode: string = tl.getInput('appPackageDistributionMode') ?? 'SideloadOnly' + const msbuildLocationMethod: string = helpers.getInputWithErrorCheck('msBuildLocationMethod', 'Method to locate MSBuild is required.'); + + let msbuildVersion: string | undefined; + let msbuildArchitecture: string | undefined; + if (msbuildLocationMethod === 'version') + { + msbuildVersion = helpers.getInputWithErrorCheck('msbuildVersion', 'Version of MSBuild to use is required.'); + msbuildArchitecture = helpers.getInputWithErrorCheck('msbuildArchitecture', 'Build architecture of MSBuild to use is required.'); + } + + let msbuildLocation: string | undefined; + if (msbuildLocationMethod === 'location') + { + msbuildLocation = helpers.getInputWithErrorCheck('msbuildLocation', 'Location of MSBuild.exe is required.'); + } + + return { + solution, + configuration, + msbuildArguments, + appxPackageBuildMode, + clean, + msbuildLocationMethod, + msbuildLocation, + msbuildVersion, + msbuildArchitecture + }; +} + +const getMSBuildPathFromVersion = async (msbuildVersion: string, msbuildArchitecture: string): Promise => +{ + const osPlatform: string = os.platform(); + if (osPlatform === 'win32') + { + // The Powershell helper only works on Windows; + // it looks in the Global Assembly Cache to find the right version. + // We use a wrapper script to call the right function from the helper. + const powershellRunner: ToolRunner = helpers.getPowershellRunner(MSBUILD_PATH_HELPER_SCRIPT); + powershellRunner.arg(['-PreferredVersion', msbuildVersion]); + powershellRunner.arg(['-Architecture', msbuildArchitecture]); + + const execResult = powershellRunner.execSync(); + if (execResult.code) + { + throw execResult.stderr; + } + + return execResult.stdout.trim(); + } + else + { + // The TypeScript helper only works on Mac and Linux. + return await msbuildHelpers.getMSBuildPath(msbuildVersion); + } +} + +const getMSBuildToolRunner = ( + msbuildToolPath: string, + solutionFile: string, + createBundle: boolean, + outputPath: string, + platforms: string[], + configuration: string, + msbuildArguments: string, + appxPackageBuildMode: string + ) : ToolRunner => +{ + const buildTool: ToolRunner = tl.tool(msbuildToolPath); + buildTool.arg(solutionFile); + + // If we don't specify a platform when bundling, MSBuild may use one we are not building and cause an error. + buildTool.arg('/p:Platform=' + platforms[0]); + buildTool.arg('/p:Configuration=' + configuration); + + buildTool.arg('/p:UapAppxPackageBuildMode=' + appxPackageBuildMode); + if (msbuildArguments) + { + buildTool.line(msbuildArguments); + } + + // Add arguments specific for bundles/packages + if (createBundle) + { + buildTool.arg('/p:AppxBundle=Always'); + buildTool.arg('/p:AppxBundleOutput=' + outputPath); + buildTool.arg('/p:AppxBundlePlatforms=' + platforms.join('|')); + } + else + { + buildTool.arg('/p:AppxBundle=Never'); + buildTool.arg('/p:AppxPackageOutput=' + outputPath); + } + + return buildTool; +} + +export const runMSBuild = async ( + createBundle: boolean, + outputPath: string, + platforms: string[], + { + solution, + configuration, + msbuildArguments, + appxPackageBuildMode, + clean, + msbuildLocationMethod, + msbuildLocation, + msbuildVersion, + msbuildArchitecture, + }: MSBuildCommonParameters) => +{ + const msbuildTool: string = msbuildLocationMethod === 'location' ? msbuildLocation! : await getMSBuildPathFromVersion(msbuildVersion!, msbuildArchitecture!); + + const filesList: string[] = tl.findMatch('', solution, { followSymbolicLinks: false, followSpecifiedSymbolicLink: false, allowBrokenSymbolicLinks: false }, { matchBase: true }); + + // We only build a single file. + const file: string = filesList[0]; + if (clean) + { + const cleanTool: ToolRunner = getMSBuildToolRunner(msbuildTool, file, createBundle, outputPath, platforms, configuration, msbuildArguments, appxPackageBuildMode); + cleanTool.arg('/t:Clean'); + await cleanTool.exec(); + } + + const buildTool: ToolRunner = getMSBuildToolRunner(msbuildTool, file, createBundle, outputPath, platforms, configuration, msbuildArguments, appxPackageBuildMode); + await buildTool.exec(); +} diff --git a/tools/pipelines-tasks/MsixPackaging/package-lock.json b/tools/pipelines-tasks/MsixPackaging/package-lock.json new file mode 100644 index 000000000..eefcd3974 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/package-lock.json @@ -0,0 +1,340 @@ +{ + "name": "msix-tasks-packaging", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "14.14.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz", + "integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw==" + }, + "@types/xml2js": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.8.tgz", + "integrity": "sha512-EyvT83ezOdec7BhDaEcsklWy7RSIdi6CNe95tmOAK0yx/Lm30C9K75snT3fYayK59ApC2oyW+rcHErdG05FHJA==", + "requires": { + "@types/node": "*" + } + }, + "adm-zip": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "azure-devops-node-api": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-10.2.2.tgz", + "integrity": "sha512-4TVv2X7oNStT0vLaEfExmy3J4/CzfuXolEcQl/BRUmvGySqKStTG2O55/hUQ0kM7UJlZBLgniM0SBq4d/WkKow==", + "requires": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "azure-pipelines-task-lib": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.12.2.tgz", + "integrity": "sha512-ofAdVZcL90Qv6zYcKa1vK3Wnrl2kxoKX/Idvb7RWrqHQzcJlAEjCU4UCB5y6NnSKqRSyVTIhdS6hChphpOaiMQ==", + "requires": { + "minimatch": "3.0.4", + "mockery": "^1.7.0", + "q": "^1.1.2", + "semver": "^5.1.0", + "shelljs": "^0.3.0", + "sync-request": "3.0.1", + "uuid": "^3.0.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "common": { + "version": "file:../common/msix-tasks-helpers-1.0.0.tgz", + "integrity": "sha512-hBVHDNpkQ6jDdAjg0jF2tnWgdqyg8R69eEchpCmiiiHVoUekFKQeYkmW+/CPQQ88kLU02CaPmV7XhDvYLUJ+0g==", + "requires": { + "@types/xml2js": "^0.4.8", + "adm-zip": "^0.5.5", + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "xml2js": "^0.4.23" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "http-basic": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-2.5.1.tgz", + "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.6", + "http-response-object": "^1.0.0" + } + }, + "http-response-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", + "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mockery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", + "integrity": "sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8=" + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "sync-request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-3.0.1.tgz", + "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", + "requires": { + "concat-stream": "^1.4.7", + "http-response-object": "^1.0.1", + "then-request": "^2.0.1" + } + }, + "then-request": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", + "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.7", + "http-basic": "^2.5.1", + "http-response-object": "^1.1.0", + "promise": "^7.1.1", + "qs": "^6.1.0" + } + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "typed-rest-client": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", + "integrity": "sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg==", + "requires": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "underscore": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.0.tgz", + "integrity": "sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + } + } +} diff --git a/tools/pipelines-tasks/MsixPackaging/package.json b/tools/pipelines-tasks/MsixPackaging/package.json new file mode 100644 index 000000000..3b3fc301d --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/package.json @@ -0,0 +1,20 @@ +{ + "name": "msix-tasks-packaging", + "version": "1.0.0", + "description": "Task for building and packaging MSIX apps", + "repository": { + "type": "git", + "url": "git+github.com/microsoft/msix-ado-tasks-extension.git" + }, + "scripts": {}, + "author": "Microsoft Coporation", + "license": "MIT", + "dependencies": { + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "common": "file:../common/msix-tasks-helpers-1.0.0.tgz" + }, + "devDependencies": { + "@types/node": "^14.14.39" + } +} diff --git a/tools/pipelines-tasks/MsixPackaging/task.json b/tools/pipelines-tasks/MsixPackaging/task.json new file mode 100644 index 000000000..f69f7ef32 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/task.json @@ -0,0 +1,222 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "e8789f65-a0e2-472b-98ca-8cfd83ccc3c3", + "name": "MsixPackaging", + "friendlyName": "MSIX build and package", + "instanceNameFormat": "MSIX build and package", + "description": "Build and package Windows apps using the MSIX package format", + "author": "Microsoft Corporation", + "category": "Package", + "helpUrl": "https://aka.ms/msix-cicd", + "helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "execution": { + "Node10": { + "target": "index.js" + } + }, + "version": { + "Major": 1, + "Minor": 0, + "Patch": 0 + }, + "demands": [ + "msbuild" + ], + "minimumAgentVersion": "1.95.0", + "inputs": [ + { + "name": "outputPath", + "type": "string", + "label": "Output Path", + "defaultValue": "", + "required": true, + "helpMarkDown": "Path of the generated package or bundle." + }, + { + "name": "buildSolution", + "type": "boolean", + "label": "Build Solution with MSBuild", + "defaultValue": "true", + "helpMarkDown": "Invoke MSBuild and build the solution from scratch. If this is selected, you must provide a path to the solution file to build and select the target platforms to build." + }, + { + "name": "inputDirectory", + "type": "string", + "label": "Input Directory", + "required": true, + "helpMarkDown": "Relative path from the repo root to the directory with the files to be packaged.", + "visibleRule": "buildSolution = false" + }, + { + "name": "solution", + "type": "filePath", + "label": "Project to Build", + "defaultValue": "**/*.sln", + "required": true, + "helpMarkDown": "Relative path from repo root of the project or solution to build. Wildcards can be used.", + "visibleRule": "buildSolution = true" + }, + { + "name": "clean", + "type": "boolean", + "label": "Clean Before Building", + "defaultValue": false, + "required": true, + "helpMarkDown": "Run a clean build (/t:clean) prior to the build.", + "visibleRule": "buildSolution = true" + }, + { + "name": "generateBundle", + "type": "boolean", + "label": "Generate MSIX Bundle", + "defaultValue": false, + "required": true, + "helpMarkDown": "Generate an MSIX bundle." + }, + { + "name": "buildConfiguration", + "type": "pickList", + "label": "Configuration", + "defaultValue": "debug", + "helpMarkDown": "The configuration to build the solution with. Only one configuration is supported at a time.", + "required": true, + "options": { + "debug": "Debug", + "release": "Release" + }, + "visibleRule": "buildSolution = true" + }, + { + "name": "buildPlatform", + "type": "string", + "label": "Platform", + "defaultValue": "", + "helpMarkDown": "Specify the platform you want to build such as Win32, x86, x64 or any cpu.", + "required": true, + "visibleRule": "buildSolution = true && generateBundle = false" + }, + { + "name": "buildForX86", + "type": "boolean", + "label": "Build for x86", + "defaultValue": true, + "helpMarkDown": "Build the solution for the x86 platform.", + "visibleRule": "buildSolution = true && generateBundle = true" + }, + { + "name": "buildForX64", + "type": "boolean", + "label": "Build for x64", + "defaultValue": true, + "helpMarkDown": "Build the solution for the x64 platform.", + "visibleRule": "buildSolution = true && generateBundle = true" + }, + { + "name": "buildForArm", + "type": "boolean", + "label": "Build for ARM", + "defaultValue": false, + "helpMarkDown": "Build the solution for the ARM platform.", + "visibleRule": "buildSolution = true && generateBundle = true" + }, + { + "name": "buildForAnyCpu", + "type": "boolean", + "label": "Build for Any CPU", + "defaultValue": false, + "helpMarkDown": "Build the solution for the Any CPU platform.", + "visibleRule": "buildSolution = true && generateBundle = true" + }, + { + "name": "updateAppVersion", + "type": "boolean", + "label": "Update App Version in Manifest", + "defaultValue": false, + "required": true, + "helpMarkDown": "Update the app version from the one in the manifest." + }, + { + "name": "manifestFile", + "type": "filePath", + "label": "Manifest File to Update Version", + "defaultValue": "", + "required": true, + "helpMarkDown": "Path to the manifest file. This is only used to update the app version.", + "visibleRule": "updateAppVersion = true" + }, + { + "name": "appVersion", + "type": "string", + "label": "App Version", + "defaultValue": "1.0.0.0", + "required": true, + "helpMarkDown": "Version number to set for the app.", + "visibleRule": "updateAppVersion = true" + }, + { + "name": "appPackageDistributionMode", + "type": "pickList", + "label": "Application Package Distribution Mode", + "defaultValue": "StoreAndSideload", + "required": true, + "helpMarkDown": "Whether to generate the .msixupload file for Store upload, the .msix/.msixbundle for non-Store apps, or both.", + "options": { + "SideloadOnly": "Create an application package (.msix) or bundle (.msixbundle) for non-Store app", + "StoreOnly": "Create an application package upload file (.msixupload) for Store app", + "StoreAndSideload": "Both" + }, + "visibleRule": "buildSolution = true" + }, + { + "name": "msbuildLocationMethod", + "type": "radio", + "label": "MSBuild", + "required": true, + "defaultValue": "version", + "options": { + "version": "Version", + "location": "Specify Location" + }, + "visibleRule": "buildSolution = true" + }, + { + "name": "msbuildVersion", + "type": "pickList", + "label": "MSBuild Version", + "required": true, + "defaultValue": "latest", + "helpMarkDown": "If the preferred version cannot be found, the latest version found will be used instead.", + "visibleRule": "buildSolution = true && msbuildLocationMethod = version", + "options": { + "latest": "Latest", + "16.0": "MSBuild 16.0", + "15.0": "MSBuild 15.0", + "14.0": "MSBuild 14.0", + "12.0": "MSBuild 12.0", + "4.0": "MSBuild 4.0" + } + }, + { + "name": "msbuildArchitecture", + "type": "pickList", + "label": "MSBuild Architecture", + "defaultValue": "x86", + "required": true, + "helpMarkDown": "Optionally supply the architecture (x86, x64) of MSBuild to run.", + "visibleRule": "buildSolution = true && msbuildLocationMethod = version", + "options": { + "x86": "MSBuild x86", + "x64": "MSBuild x64" + } + }, + { + "name": "msbuildLocation", + "type": "string", + "label": "Path to MSBuild", + "defaultValue": "", + "required": true, + "helpMarkDown": "Optionally supply the path to MSBuild.", + "visibleRule": "buildSolution = true && msbuildLocationMethod = location" + } + ] +} diff --git a/tools/pipelines-tasks/MsixPackaging/task.loc.json b/tools/pipelines-tasks/MsixPackaging/task.loc.json new file mode 100644 index 000000000..f55811dc6 --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/task.loc.json @@ -0,0 +1,222 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "e8789f65-a0e2-472b-98ca-8cfd83ccc3c3", + "name": "MsixPackaging", + "friendlyName": "ms-resource:loc.friendlyName", + "instanceNameFormat": "ms-resource:loc.instanceNameFormat", + "description": "ms-resource:loc.description", + "author": "Microsoft Corporation", + "category": "Package", + "helpUrl": "https://aka.ms/msix-cicd", + "helpMarkDown": "ms-resource:loc.helpMarkDown", + "execution": { + "Node10": { + "target": "index.js" + } + }, + "version": { + "Major": 1, + "Minor": 0, + "Patch": 0 + }, + "demands": [ + "msbuild" + ], + "minimumAgentVersion": "1.95.0", + "inputs": [ + { + "name": "outputPath", + "type": "string", + "label": "ms-resource:loc.input.label.outputPath", + "defaultValue": "", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.outputPath" + }, + { + "name": "buildSolution", + "type": "boolean", + "label": "ms-resource:loc.input.label.buildSolution", + "defaultValue": "true", + "helpMarkDown": "ms-resource:loc.input.help.buildSolution" + }, + { + "name": "inputDirectory", + "type": "string", + "label": "ms-resource:loc.input.label.inputDirectory", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.inputDirectory", + "visibleRule": "buildSolution = false" + }, + { + "name": "solution", + "type": "filePath", + "label": "ms-resource:loc.input.label.solution", + "defaultValue": "**/*.sln", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.solution", + "visibleRule": "buildSolution = true" + }, + { + "name": "clean", + "type": "boolean", + "label": "ms-resource:loc.input.label.clean", + "defaultValue": false, + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.clean", + "visibleRule": "buildSolution = true" + }, + { + "name": "generateBundle", + "type": "boolean", + "label": "ms-resource:loc.input.label.generateBundle", + "defaultValue": false, + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.generateBundle" + }, + { + "name": "buildConfiguration", + "type": "pickList", + "label": "ms-resource:loc.input.label.buildConfiguration", + "defaultValue": "debug", + "helpMarkDown": "ms-resource:loc.input.help.buildConfiguration", + "required": true, + "options": { + "debug": "Debug", + "release": "Release" + }, + "visibleRule": "buildSolution = true" + }, + { + "name": "buildPlatform", + "type": "string", + "label": "ms-resource:loc.input.label.buildPlatform", + "defaultValue": "", + "helpMarkDown": "ms-resource:loc.input.help.buildPlatform", + "required": true, + "visibleRule": "buildSolution = true && generateBundle = false" + }, + { + "name": "buildForX86", + "type": "boolean", + "label": "ms-resource:loc.input.label.buildForX86", + "defaultValue": true, + "helpMarkDown": "ms-resource:loc.input.help.buildForX86", + "visibleRule": "buildSolution = true && generateBundle = true" + }, + { + "name": "buildForX64", + "type": "boolean", + "label": "ms-resource:loc.input.label.buildForX64", + "defaultValue": true, + "helpMarkDown": "ms-resource:loc.input.help.buildForX64", + "visibleRule": "buildSolution = true && generateBundle = true" + }, + { + "name": "buildForArm", + "type": "boolean", + "label": "ms-resource:loc.input.label.buildForArm", + "defaultValue": false, + "helpMarkDown": "ms-resource:loc.input.help.buildForArm", + "visibleRule": "buildSolution = true && generateBundle = true" + }, + { + "name": "buildForAnyCpu", + "type": "boolean", + "label": "ms-resource:loc.input.label.buildForAnyCpu", + "defaultValue": false, + "helpMarkDown": "ms-resource:loc.input.help.buildForAnyCpu", + "visibleRule": "buildSolution = true && generateBundle = true" + }, + { + "name": "updateAppVersion", + "type": "boolean", + "label": "ms-resource:loc.input.label.updateAppVersion", + "defaultValue": false, + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.updateAppVersion" + }, + { + "name": "manifestFile", + "type": "filePath", + "label": "ms-resource:loc.input.label.manifestFile", + "defaultValue": "", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.manifestFile", + "visibleRule": "updateAppVersion = true" + }, + { + "name": "appVersion", + "type": "string", + "label": "ms-resource:loc.input.label.appVersion", + "defaultValue": "1.0.0.0", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.appVersion", + "visibleRule": "updateAppVersion = true" + }, + { + "name": "appPackageDistributionMode", + "type": "pickList", + "label": "ms-resource:loc.input.label.appPackageDistributionMode", + "defaultValue": "StoreAndSideload", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.appPackageDistributionMode", + "options": { + "SideloadOnly": "Create an application package (.msix) or bundle (.msixbundle) for non-Store app", + "StoreOnly": "Create an application package upload file (.msixupload) for Store app", + "StoreAndSideload": "Both" + }, + "visibleRule": "buildSolution = true" + }, + { + "name": "msbuildLocationMethod", + "type": "radio", + "label": "ms-resource:loc.input.label.msbuildLocationMethod", + "required": true, + "defaultValue": "version", + "options": { + "version": "Version", + "location": "Specify Location" + }, + "visibleRule": "buildSolution = true" + }, + { + "name": "msbuildVersion", + "type": "pickList", + "label": "ms-resource:loc.input.label.msbuildVersion", + "required": true, + "defaultValue": "latest", + "helpMarkDown": "ms-resource:loc.input.help.msbuildVersion", + "visibleRule": "buildSolution = true && msbuildLocationMethod = version", + "options": { + "latest": "Latest", + "16.0": "MSBuild 16.0", + "15.0": "MSBuild 15.0", + "14.0": "MSBuild 14.0", + "12.0": "MSBuild 12.0", + "4.0": "MSBuild 4.0" + } + }, + { + "name": "msbuildArchitecture", + "type": "pickList", + "label": "ms-resource:loc.input.label.msbuildArchitecture", + "defaultValue": "x86", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.msbuildArchitecture", + "visibleRule": "buildSolution = true && msbuildLocationMethod = version", + "options": { + "x86": "MSBuild x86", + "x64": "MSBuild x64" + } + }, + { + "name": "msbuildLocation", + "type": "string", + "label": "ms-resource:loc.input.label.msbuildLocation", + "defaultValue": "", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.msbuildLocation", + "visibleRule": "buildSolution = true && msbuildLocationMethod = location" + } + ] +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixPackaging/tsconfig.json b/tools/pipelines-tasks/MsixPackaging/tsconfig.json new file mode 100644 index 000000000..3c43903cf --- /dev/null +++ b/tools/pipelines-tasks/MsixPackaging/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json" +} diff --git a/tools/pipelines-tasks/MsixSigning/ImportCert.ps1 b/tools/pipelines-tasks/MsixSigning/ImportCert.ps1 new file mode 100644 index 000000000..1f11422dd --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/ImportCert.ps1 @@ -0,0 +1,44 @@ +<# +.Description +Adds or removes a certificate encoded as a string to/from the default cert store. + +.Outputs +System.String. The thumbprint of the certificate. +#> + +using namespace System.Security.Cryptography.X509Certificates + +[CmdletBinding()] +param ( + # Contents of the .pfx certificate file encoded as a base64 string. + [Parameter()] + [string] + $certBase64, + + # Remove the certificate from the cert store instead of adding it. + [Parameter()] + [switch] + $remove +) + +# Convert the base64 string to a certificate object. +# We want to persist the private key to the cert store when we import it to be able to sign. +$certBytes = [System.Convert]::FromBase64String($certBase64.Trim()) +$cert = New-Object -TypeName X509Certificate2 -ArgumentList ($certBytes, $null, [X509KeyStorageFlags]::PersistKeySet) -ErrorAction Stop + +# Open the default cert store. This is the current user's "My" (Personal) store. +$certStore = New-Object -TypeName X509Store +$certStore.Open([OpenFlags]::OpenExistingOnly -bor [OpenFlags]::ReadWrite) + +# Add or remove the certificate as needed. +if ($remove) +{ + $certStore.Remove($cert) +} +else +{ + $certStore.Add($cert) +} + +$certStore.Close() +return $cert.Thumbprint \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/de-DE/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/de-DE/resources.resjson new file mode 100644 index 000000000..ab903229b --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/de-DE/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "MSIX-Paketsignierung", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "MSIX-Pakete signieren", + "loc.instanceNameFormat": "MSIX-Paket signieren", + "loc.input.label.package": "Zu signierendes Paket", + "loc.input.help.package": "Zu signierende Dateien. Kann ein Paket oder Bundle sein. Alle passenden Dateien werden signiert.", + "loc.input.label.certificateType": "Zertifikats-Dateityp", + "loc.input.help.certificateType": "Quelle der zu verwendenden Zertifikatsdatei. Das Zertifikat kann aus dem [Secure Files library](https://docs.microsoft.com/azure/devops/pipelines/library/secure-files) stammen oder als String kodiert sein, als ob es mit dem [Azure Key Vault task](https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault) abgerufen wird.", + "loc.input.label.encodedCertificate": "Kodierte Zertifikatsdatei", + "loc.input.help.encodedCertificate": "Zertifikat kodiert als Base64-Zeichenfolge.", + "loc.input.label.certificate": "Zertifikatsdatei", + "loc.input.help.certificate": "Der Dateiname oder der GUID des sicheren Zertifikats, das zum Signieren verwendet wird. Die Datei wird nach dem Ausführen der Pipeline gelöscht.", + "loc.input.label.passwordVariable": "Kennwortvariable", + "loc.input.help.passwordVariable": "Der Name der Variablen, in der das Passwort gespeichert ist, das für den Zugriff auf die Zertifikatsdatei zum Signieren verwendet wird. Beachten Sie, dass dies NICHT das Passwort selbst ist, sondern der Variablenname, der unter „Bibliothek“ festgelegt werden kann.", + "loc.input.label.timeStampServer": "Zeitstempelserver", + "loc.input.help.timeStampServer": "Eine URL, die die Adresse eines Zeitstempelservers angibt." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/en-US/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/en-US/resources.resjson new file mode 100644 index 000000000..0ec2cb39e --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/en-US/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "MSIX package signing", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Sign MSIX packages", + "loc.instanceNameFormat": "Sign MSIX package", + "loc.input.label.package": "Package to sign", + "loc.input.help.package": "Files to sign. Can be a package or a bundle. All matching files are signed.", + "loc.input.label.certificateType": "Certificate File type", + "loc.input.help.certificateType": "Source of the certificate file to use. The certificate can come from the [Secure Files library](https://docs.microsoft.com/azure/devops/pipelines/library/secure-files), or be encoded as a string as if fetched with the [Azure Key Vault task](https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault)", + "loc.input.label.encodedCertificate": "Encoded Certificate File", + "loc.input.help.encodedCertificate": "Certificate encoded as a Base64 string.", + "loc.input.label.certificate": "Certificate File", + "loc.input.help.certificate": "The file name or GUID of the secure certificate to use for signing. The file will be deleted after the pipeline runs.", + "loc.input.label.passwordVariable": "Password Variable", + "loc.input.help.passwordVariable": "The name of the variable which stores the password used to access the certificate file for signing. Note that this is NOT the password itself, but rather the variable name, which can be set under Library.", + "loc.input.label.timeStampServer": "Time Stamp Server", + "loc.input.help.timeStampServer": "A URL that specifies the address of a time stamping server." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/es-ES/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/es-ES/resources.resjson new file mode 100644 index 000000000..f92c47129 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/es-ES/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "Firmas de paquete MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Firmar paquetes MSIX", + "loc.instanceNameFormat": "Firmar paquete MSIX", + "loc.input.label.package": "Paquete para firmar", + "loc.input.help.package": "Archivos para firmar. Puede ser un paquete o un empaquetado. Se firman todos los archivos coincidentes.", + "loc.input.label.certificateType": "Tipo de archivo de certificado", + "loc.input.help.certificateType": "Origen del archivo de certificado que se va a usar. El certificado puede provenir de [Secure Files library] (https://docs.microsoft.com/azure/devops/pipelines/library/secure-files), o codificarse como una cadena como si se obtuviera con [Azure Key Vault task] (https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault)", + "loc.input.label.encodedCertificate": "Archivo de certificado codificado", + "loc.input.help.encodedCertificate": "Certificado codificado como una cadena Base64.", + "loc.input.label.certificate": "Archivo de certificado", + "loc.input.help.certificate": "El nombre de archivo o GUID del certificado seguro para usar para la firma. El archivo será eliminado después de que la canalización se ejecute.", + "loc.input.label.passwordVariable": "Variable de contraseña", + "loc.input.help.passwordVariable": "El nombre de la variable que almacena la contraseña utilizada para acceder al archivo del certificado para la firma. Tenga en cuenta que no se trata de la contraseña en sí, sino del nombre de la variable, que se puede establecer en biblioteca.", + "loc.input.label.timeStampServer": "Servidor de marca de tiempo", + "loc.input.help.timeStampServer": "Una dirección URL que especifica la dirección de un servidor de marca de tiempo." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/fr-FR/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/fr-FR/resources.resjson new file mode 100644 index 000000000..b59747b4b --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/fr-FR/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "Signature du package MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Signer les packages MSIX", + "loc.instanceNameFormat": "Signer le package MSIX", + "loc.input.label.package": "Package à signer", + "loc.input.help.package": "Fichiers à signer. Peut être un package ou un bundle. Tous les fichiers correspondants sont signés.", + "loc.input.label.certificateType": "Type de fichier de certificat", + "loc.input.help.certificateType": "Source du fichier de certificat à utiliser. Le certificat peut provenir de [Secure Files library] (https://docs.microsoft.com/azure/devops/pipelines/library/secure-files) ou être encodé sous la forme d’une chaîne comme si elle était récupérée avec le [Azure Key Vault task] (https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault)", + "loc.input.label.encodedCertificate": "Fichier de certificat codé", + "loc.input.help.encodedCertificate": "Certificat codé en tant que chaîne Base64.", + "loc.input.label.certificate": "Fichier de certificats", + "loc.input.help.certificate": "Nom de fichier ou GUID du certificat sécurisé à utiliser pour la signature. Le fichier est supprimé après exécution du pipeline.", + "loc.input.label.passwordVariable": "Variable du mot de passe", + "loc.input.help.passwordVariable": "Nom de la variable qui stocke le mot de passe utilisé pour accéder au fichier de certificat pour la signature. Notez que ce n’est PAS le mot de passe proprement dit, mais plutôt le nom de la variable, qui peut être positionné sous Bibliothèque.", + "loc.input.label.timeStampServer": "Serveur d’horodatage", + "loc.input.help.timeStampServer": "URL qui indique l’adresse d’un serveur d’horodatage des heures." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/it-IT/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/it-IT/resources.resjson new file mode 100644 index 000000000..a21381b63 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/it-IT/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "Firma pacchetto MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Firma pacchetti MSIX", + "loc.instanceNameFormat": "Firma pacchetto MSIX", + "loc.input.label.package": "Pacchetto da firmare", + "loc.input.help.package": "File da firmare. Può trattarsi di un pacchetto o di un bundle. Tutti i file corrispondenti sono firmati.", + "loc.input.label.certificateType": "Tipo del file del certificato", + "loc.input.help.certificateType": "Origine del file del certificato da usare. Il certificato può essere ottenuto da [Secure Files library](https://docs.microsoft.com/azure/devops/pipelines/library/secure-files) o essere codificato come stringa, come se fosse recuperato con [Azure Key Vault task](https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault)", + "loc.input.label.encodedCertificate": "File del certificato codificato", + "loc.input.help.encodedCertificate": "Certificato codificato come stringa Base64.", + "loc.input.label.certificate": "File del certificato", + "loc.input.help.certificate": "Nome file o GUID del certificato sicuro da usare per la firma. Il file verrà eliminato dopo l'esecuzione della pipeline.", + "loc.input.label.passwordVariable": "Variabile password", + "loc.input.help.passwordVariable": "Nome della variabile che archivia la password usata per accedere al file del certificato per la firma. Si noti che questa NON è la password stessa, ma piuttosto il nome della variabile, che può essere impostata in Raccolta.", + "loc.input.label.timeStampServer": "Server timestamp", + "loc.input.help.timeStampServer": "URL che specifica l'indirizzo di un server di timestamp." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/ja-JP/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/ja-JP/resources.resjson new file mode 100644 index 000000000..39b019aff --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/ja-JP/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "MSIX パッケージの署名", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "MSIX パッケージに署名する", + "loc.instanceNameFormat": "MSIX パッケージに署名する", + "loc.input.label.package": "署名するパッケージ", + "loc.input.help.package": "署名するファイルです。パッケージの場合も、バンドルの場合もあります。一致するファイルはすべて署名されます。", + "loc.input.label.certificateType": "証明書ファイルの種類", + "loc.input.help.certificateType": "使用する証明書ファイルのソースです。証明書は、[Secure Files ライブラリ](https://docs.microsoft.com/azure/devops/pipelines/library/secure-files)から取得するか、[Azure Key Vault タスク](https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault)で取得したかのように文字列としてエンコードすることができます", + "loc.input.label.encodedCertificate": "エンコードされた証明書ファイル", + "loc.input.help.encodedCertificate": "証明書は Base64 文字列としてエンコードされています。", + "loc.input.label.certificate": "証明書ファイル", + "loc.input.help.certificate": "署名で使用する、セキュリティ証明書のファイル名または GUID です。ファイルは、パイプライン実行後に削除されます。", + "loc.input.label.passwordVariable": "パスワード変数", + "loc.input.help.passwordVariable": "署名を行うために証明書ファイルにアクセスするのに使用されるパスワードを格納する変数の名前です。これ自体はパスワードではなく、ライブラリで指定することができる変数名であることに注意してください。", + "loc.input.label.timeStampServer": "タイム ​​スタンプ サーバー", + "loc.input.help.timeStampServer": "タイムスタンプサーバーのアドレスを指定する URL です。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/ko-KR/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/ko-KR/resources.resjson new file mode 100644 index 000000000..3a7f6f430 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/ko-KR/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "MSIX 패키지 서명", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "MSIX 패키지 서명", + "loc.instanceNameFormat": "MSIX 패키지 서명", + "loc.input.label.package": "서명할 패키지", + "loc.input.help.package": "서명할 파일입니다. 패키지 또는 번들일 수 있습니다. 일치하는 모든 파일이 서명됩니다.", + "loc.input.label.certificateType": "인증서 파일 형식", + "loc.input.help.certificateType": "사용할 인증서 파일의 원본입니다. 인증서는 [Secure Files library](https://docs.microsoft.com/azure/devops/pipelines/library/secure-files)에서 가져오거나 [Azure Key Vault task](https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault)(으)로 가져온 것처럼 문자열로 인코딩될 수 있습니다.", + "loc.input.label.encodedCertificate": "인코딩된 인증서 파일", + "loc.input.help.encodedCertificate": "Base64 문자열로 인코딩된 인증서입니다.", + "loc.input.label.certificate": "인증서 파일", + "loc.input.help.certificate": "서명에 사용할 보안 인증서의 파일 이름 또는 GUID입니다. 파이프라인이 실행된 후 파일이 삭제됩니다.", + "loc.input.label.passwordVariable": "암호 변수", + "loc.input.help.passwordVariable": "서명을 위해 인증서 파일에 액세스하는 데 사용되는 암호를 저장하는 변수의 이름입니다. 이것은 암호 자체가 아니라, 라이브러리에서 설정할 수 있는 변수 이름입니다.", + "loc.input.label.timeStampServer": "타임스탬프 서버", + "loc.input.help.timeStampServer": "타임스탬프 서버의 주소를 지정하는 URL입니다." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/pt-BR/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/pt-BR/resources.resjson new file mode 100644 index 000000000..3af5f6556 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/pt-BR/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "Assinatura de pacote MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Assinar pacotes do MSIX", + "loc.instanceNameFormat": "Assinar pacote do MSIX", + "loc.input.label.package": "Pacote para assinar", + "loc.input.help.package": "Arquivos a assinar. Pode ser um pacote ou um lote. Todos os arquivos correspondentes são assinados.", + "loc.input.label.certificateType": "Tipo de Arquivo de certificado", + "loc.input.help.certificateType": "Origem do arquivo de certificado a ser usado. O certificado pode vir de [Secure Files library] (https://docs.microsoft.com/azure/devops/pipelines/library/secure-files), ou ser codificado como uma cadeia de caracteres como se fosse buscado em [Azure Key Vault task] (https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault)", + "loc.input.label.encodedCertificate": "Arquivo de certificado codificado", + "loc.input.help.encodedCertificate": "Certificado codificado como uma sequência Base64.", + "loc.input.label.certificate": "Arquivo de Certificado", + "loc.input.help.certificate": "O nome de arquivo ou GUID do certificado seguro a ser usado na assinatura. O arquivo será excluído após a execução do pipeline.", + "loc.input.label.passwordVariable": "Variável de senha", + "loc.input.help.passwordVariable": "O nome da variável que armazena a senha usada para acessar o arquivo de certificado para assinatura. Observe que essa NÃO é a senha em si, mas o nome da variável, que pode ser definido em Biblioteca.", + "loc.input.label.timeStampServer": "Servidor de Carimbo de Data/Hora", + "loc.input.help.timeStampServer": "Uma URL que especifica o endereço de um servidor de carimbo de data/hora." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/ru-RU/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/ru-RU/resources.resjson new file mode 100644 index 000000000..4c0624797 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/ru-RU/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "Подписывание пакета MSIX", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "Подписывать пакеты MSIX", + "loc.instanceNameFormat": "Подписать пакет MSIX", + "loc.input.label.package": "Пакет для подписи", + "loc.input.help.package": "Файлы для подписывания. Могут быть пакетом или набором. Все соответствующие файлы подписаны.", + "loc.input.label.certificateType": "Тип файла сертификата", + "loc.input.help.certificateType": "Источник используемого файла сертификата. Сертификат может поступить из [Secure Files library](https://docs.microsoft.com/azure/devops/pipelines/library/secure-files) или быть зашифрован как строка при получении с помощью [Azure Key Vault task](https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault)", + "loc.input.label.encodedCertificate": "Файл зашифрованного сертификата", + "loc.input.help.encodedCertificate": "Сертификат закодирован как строка в кодировке Base64.", + "loc.input.label.certificate": "Файл сертификата", + "loc.input.help.certificate": "Имя файла или идентификатор GUID сертификата безопасности, используемого для подписи. Файл будет удален после запуска конвейера.", + "loc.input.label.passwordVariable": "Переменная пароля", + "loc.input.help.passwordVariable": "Имя переменной, в которой хранится пароль, используемый для доступа к файлу сертификата для подписи. Обратите внимание, что это НЕ сам пароль, а имя переменной, которое можно задать в библиотеке.", + "loc.input.label.timeStampServer": "Сервер меток времени", + "loc.input.help.timeStampServer": "URL-адрес, указывающий адрес сервера метки времени." +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/zh-CN/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/zh-CN/resources.resjson new file mode 100644 index 000000000..a388b2512 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/zh-CN/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "MSIX 程序包签名", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "签署 MSIX 程序包", + "loc.instanceNameFormat": "签署 MSIX 程序包", + "loc.input.label.package": "要签署的程序包", + "loc.input.help.package": "要签名的文件。可以是一个程序包或捆绑包。已对所有匹配的文件进行签名。", + "loc.input.label.certificateType": "证书文件类型", + "loc.input.help.certificateType": "要使用的证书文件的来源。证书可以来自 [Secure Files library](https://docs.microsoft.com/azure/devops/pipelines/library/secure-files),或将其编码为字符串,如同使用 [Azure Key Vault task](https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault) 获取的一样", + "loc.input.label.encodedCertificate": "已编码的证书文件", + "loc.input.help.encodedCertificate": "编码为 Base64 字符串的证书。", + "loc.input.label.certificate": "证书文件", + "loc.input.help.certificate": "用于签名的安全证书的文件名或 GUID。该文件将在管道运行后删除。", + "loc.input.label.passwordVariable": "密码变量", + "loc.input.help.passwordVariable": "存储用于访问用于签名的证书文件的密码的变量名称。请注意,这不是密码本身,而是变量名称,可以在“库”下设置。", + "loc.input.label.timeStampServer": "时间戳服务器", + "loc.input.help.timeStampServer": "指定时间戳服务器地址的 URL。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/zh-TW/resources.resjson b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/zh-TW/resources.resjson new file mode 100644 index 000000000..03d3c54d1 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/Strings/resources.resjson/zh-TW/resources.resjson @@ -0,0 +1,18 @@ +{ + "loc.friendlyName": "MSIX 套件簽署", + "loc.helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "loc.description": "簽署 MSIX 套件", + "loc.instanceNameFormat": "簽署 MSIX 套件", + "loc.input.label.package": "要簽署的套件", + "loc.input.help.package": "要簽署的檔案。可以是套件或套件組合。已簽署所有相符的檔案。", + "loc.input.label.certificateType": "憑證檔案類型", + "loc.input.help.certificateType": "要使用的憑證檔案來源。憑證可以來自 [Secure Files library](https://docs.microsoft.com/azure/devops/pipelines/library/secure-files),或以字串形式編碼,就如同使用 [Azure Key Vault task](https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault) 擷取。", + "loc.input.label.encodedCertificate": "已編碼憑證檔案", + "loc.input.help.encodedCertificate": "已將憑證編碼為 Base64 字串。", + "loc.input.label.certificate": "憑證檔案", + "loc.input.help.certificate": "用於簽署的安全憑證檔案名稱或 GUID。該檔案會在管道執行之後刪除。", + "loc.input.label.passwordVariable": "密碼變數", + "loc.input.help.passwordVariable": "儲存用來存取憑證檔案以進行簽署之密碼的變數名稱。請注意,這不是密碼本身,而是可以在文件庫下設定的變數名稱。", + "loc.input.label.timeStampServer": "時間戳記伺服器", + "loc.input.help.timeStampServer": "指定時間戳記伺服器位址的 URL。" +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/debug_inputs.env b/tools/pipelines-tasks/MsixSigning/debug_inputs.env new file mode 100644 index 000000000..80a1d94c6 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/debug_inputs.env @@ -0,0 +1,10 @@ +# Set the task's inputs here to use when debugging + +INPUT_PACKAGE=test\assets\existingPackage.msix + +INPUT_CERTIFICATETYPE=secureFile +INPUT_CERTIFICATE=test\assets\certificate.pfx + +INPUT_PASSWORDVARIABLE=secretPasswordVariable +SECRET_SECRETPASSWORDVARIABLE=password +INPUT_TIMESTAMPSERVER= \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/icon.png b/tools/pipelines-tasks/MsixSigning/icon.png new file mode 100644 index 000000000..052291f00 Binary files /dev/null and b/tools/pipelines-tasks/MsixSigning/icon.png differ diff --git a/tools/pipelines-tasks/MsixSigning/index.ts b/tools/pipelines-tasks/MsixSigning/index.ts new file mode 100644 index 000000000..491fc8bbc --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/index.ts @@ -0,0 +1,209 @@ +import path = require('path'); +import tl = require('azure-pipelines-task-lib/task') +import { ToolRunner } from 'azure-pipelines-task-lib/toolrunner'; + +import helpers = require('common/helpers'); + +import download = require('./predownloadsecurefile'); + +const SIGNTOOL_PATH = path.join(__dirname, 'lib', 'signtool'); +const IMPORT_CERT_SCRIPT_PATH = path.join(__dirname, 'ImportCert.ps1'); + +/** + * Definition of how to sign with a kind of certificate (e.g. file or file encoded as string). + * Implementations of this should read the needed task inputs during construction. + */ +interface SigningType +{ + /** + * Prepares the certificate to use. + */ + prepareCert(): Promise, + /** + * Does any cleanup needed for the certificate. + */ + cleanupCert(): void, + /** + * Adds the signtool.exe arguments that specify which certificate to use. + */ + addSignToolCertOptions(signtoolRunner: ToolRunner): void +} + +/** + * Sign with a .pfx file downloaded from the pipeline's secure files. + */ +class SecureFileSigningType implements SigningType +{ + // ID of the secure .pfx file to download. + secureFileId: string; + // Password to the .pfx. + password?: string; + // Path to the downloaded file + certFilePath?: string; + + constructor() + { + this.secureFileId = tl.getInput('certificate', /* required */ true)!; + + // Get the certificate password. + // Instead of parsing a password for the certificate as a plain string, we attempt to get the password as + // a secret variable saved to the pipeline from a variable group. + // No password variable means the certificate doesn't need a password. + const passwordVariable: string | undefined = tl.getInput('passwordVariable'); + if (passwordVariable) + { + this.password = tl.getVariable(passwordVariable); + if (this.password === undefined) + { + throw Error('The secret variable given does not point to a valid password.'); + } + } + } + + // Download the pfx file + async prepareCert(): Promise + { + this.certFilePath = await download.downloadSecureFile(this.secureFileId); + } + + // Delete the downloaded file + cleanupCert() + { + download.deleteSecureFile(this.secureFileId); + } + + // Pass the cert path and password to signtool + addSignToolCertOptions(signtoolRunner: ToolRunner): void + { + signtoolRunner.arg(['/f', this.certFilePath!]); + if (this.password) + { + signtoolRunner.arg(['/p', this.password]); + } + } +} + +/** + * Sign with a pfx encoded as a string, as downloaded from Azure Key Vault + */ +class Base64EncodedCertSigningType implements SigningType +{ + // Certificate encoded as a string + base64String: string; + // Certificate hash/thumbprint for identification + certThumbprint?: string; + + constructor() + { + this.base64String = tl.getInput('encodedCertificate', /* required */ true)!; + } + + // Import the certificate into the cert store and get its thumbprint + async prepareCert(): Promise + { + const powershellRunner = helpers.getPowershellRunner(IMPORT_CERT_SCRIPT_PATH); + powershellRunner.arg(this.base64String); + + const result = powershellRunner.execSync(); + if (result.code) + { + throw result.stderr; + } + + this.certThumbprint = result.stdout.trim(); + tl.debug('cert thumbprint: ' + this.certThumbprint); + } + + // Remove the certificate from the cert store + cleanupCert() + { + const powershellRunner = helpers.getPowershellRunner(IMPORT_CERT_SCRIPT_PATH); + powershellRunner.arg(this.base64String); + powershellRunner.arg('-remove'); + powershellRunner.execSync(); + } + + // Pass the cert thumbprint to signtool + addSignToolCertOptions(signtoolRunner: ToolRunner): void + { + signtoolRunner.arg(['/sha1', this.certThumbprint!]); + } +} + +/** + * Signs a package or bundle. + * @param packagePath Path to the package to sign. + * @param certificateFilePath Path to the certificate to use for signing. + * @param timeStampServer Time stamp server to use. Can be undefined if no time stamp is needed. + * @param password Password for the certificate. Can be undefined if not needed. + */ +const signPackage = async (packagePath: string, signingType: SigningType, timeStampServer: string | undefined) => +{ + const signtoolRunner: ToolRunner = tl.tool(SIGNTOOL_PATH); + + // Base arguments + signtoolRunner.line('sign /debug /v /a /fd SHA256'); + + // Certificate to use. + signingType.addSignToolCertOptions(signtoolRunner); + + // Time stamp options + if (timeStampServer) + { + signtoolRunner.arg(['/tr', timeStampServer, '/td', 'SHA256']); + } + + signtoolRunner.arg(packagePath); + await signtoolRunner.exec(); +} + +/** + * Main function for the task. + */ +const run = async () => +{ + tl.setResourcePath(path.join(__dirname, 'task.json')); + + const packagePathPattern: string = tl.getInput('package', /* required */ true)!; + + // Get the certificate info. + const certificateType: string | undefined = tl.getInput('certificateType'); + var signingType: SigningType; + if (certificateType == 'base64') + { + tl.debug('Using base64-encoded certificate'); + signingType = new Base64EncodedCertSigningType(); + } + else + { + tl.debug('Using secure file certificate'); + signingType = new SecureFileSigningType(); + } + + // No time stamp server means to not add a time stamp. + const timeStampServer: string | undefined = tl.getInput('timeStampServer'); + + // Resolve the package paths. + const packagesToSign: string[] = tl.findMatch(/* defaultRoot */ '', packagePathPattern); + + try + { + await signingType.prepareCert(); + + // Sign the packages + for (const packagePath of packagesToSign) + { + tl.debug('signing ' + packagePath); + await signPackage(packagePath, signingType, timeStampServer); + } + } + finally + { + signingType.cleanupCert(); + } +} + +run().catch(err => + { + tl.setResult(tl.TaskResult.Failed, err.message); + }) \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/lib/signtool.exe b/tools/pipelines-tasks/MsixSigning/lib/signtool.exe new file mode 100644 index 000000000..5eb614b10 Binary files /dev/null and b/tools/pipelines-tasks/MsixSigning/lib/signtool.exe differ diff --git a/tools/pipelines-tasks/MsixSigning/package-lock.json b/tools/pipelines-tasks/MsixSigning/package-lock.json new file mode 100644 index 000000000..38bdb48ad --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/package-lock.json @@ -0,0 +1,346 @@ +{ + "name": "msix-tasks-signing", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "14.14.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz", + "integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw==" + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "dev": true + }, + "@types/xml2js": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.8.tgz", + "integrity": "sha512-EyvT83ezOdec7BhDaEcsklWy7RSIdi6CNe95tmOAK0yx/Lm30C9K75snT3fYayK59ApC2oyW+rcHErdG05FHJA==", + "requires": { + "@types/node": "*" + } + }, + "adm-zip": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "azure-devops-node-api": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-10.2.2.tgz", + "integrity": "sha512-4TVv2X7oNStT0vLaEfExmy3J4/CzfuXolEcQl/BRUmvGySqKStTG2O55/hUQ0kM7UJlZBLgniM0SBq4d/WkKow==", + "requires": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "azure-pipelines-task-lib": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.12.2.tgz", + "integrity": "sha512-ofAdVZcL90Qv6zYcKa1vK3Wnrl2kxoKX/Idvb7RWrqHQzcJlAEjCU4UCB5y6NnSKqRSyVTIhdS6hChphpOaiMQ==", + "requires": { + "minimatch": "3.0.4", + "mockery": "^1.7.0", + "q": "^1.1.2", + "semver": "^5.1.0", + "shelljs": "^0.3.0", + "sync-request": "3.0.1", + "uuid": "^3.0.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "common": { + "version": "file:../common/msix-tasks-helpers-1.0.0.tgz", + "integrity": "sha512-hBVHDNpkQ6jDdAjg0jF2tnWgdqyg8R69eEchpCmiiiHVoUekFKQeYkmW+/CPQQ88kLU02CaPmV7XhDvYLUJ+0g==", + "requires": { + "@types/xml2js": "^0.4.8", + "adm-zip": "^0.5.5", + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "xml2js": "^0.4.23" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "http-basic": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-2.5.1.tgz", + "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.6", + "http-response-object": "^1.0.0" + } + }, + "http-response-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", + "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mockery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", + "integrity": "sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8=" + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "sync-request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-3.0.1.tgz", + "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", + "requires": { + "concat-stream": "^1.4.7", + "http-response-object": "^1.0.1", + "then-request": "^2.0.1" + } + }, + "then-request": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", + "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.7", + "http-basic": "^2.5.1", + "http-response-object": "^1.1.0", + "promise": "^7.1.1", + "qs": "^6.1.0" + } + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "typed-rest-client": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", + "integrity": "sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg==", + "requires": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "underscore": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.0.tgz", + "integrity": "sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + } + } +} diff --git a/tools/pipelines-tasks/MsixSigning/package.json b/tools/pipelines-tasks/MsixSigning/package.json new file mode 100644 index 000000000..432bfeaf2 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/package.json @@ -0,0 +1,21 @@ +{ + "name": "msix-tasks-signing", + "version": "1.0.0", + "description": "Task to sign MSIX packages", + "repository": { + "type": "git", + "url": "git+github.com/microsoft/msix-ado-tasks-extension.git" + }, + "scripts": {}, + "author": "Microsoft Coporation", + "license": "MIT", + "dependencies": { + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "common": "file:../common/msix-tasks-helpers-1.0.0.tgz" + }, + "devDependencies": { + "@types/node": "^14.14.39", + "@types/q": "^1.5.4" + } +} diff --git a/tools/pipelines-tasks/MsixSigning/predownloadsecurefile.ts b/tools/pipelines-tasks/MsixSigning/predownloadsecurefile.ts new file mode 100644 index 000000000..28a409049 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/predownloadsecurefile.ts @@ -0,0 +1,34 @@ +import tl = require('azure-pipelines-task-lib/task') + +import secureFilesCommon = require('./securefiles-common/securefiles-common'); + +/** + * A small wrapper around the secure file helpers. + * This exists for easy mocking in unit tests. + */ +export const downloadSecureFile = async (secureFileId: string): Promise => +{ + // For local debugging. + if (process.argv.includes("--mockSecureFileDownload")) + { + return secureFileId; + } + + console.log("downloading file with secureFileId:", secureFileId); + const secureFileHelpers = new secureFilesCommon.SecureFileHelpers(); + const secureFilePath: string = await secureFileHelpers.downloadSecureFile(secureFileId); + console.log("file downloaded"); + return secureFilePath; +} + +export const deleteSecureFile = (secureFileId: string): void => +{ + // For local debugging don't delete. + if (process.argv.includes("--mockSecureFileDownload")) + { + return; + } + + const secureFileHelpers = new secureFilesCommon.SecureFileHelpers(); + secureFileHelpers.deleteSecureFile(secureFileId); +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/securefiles-common/securefiles-common-mock.ts b/tools/pipelines-tasks/MsixSigning/securefiles-common/securefiles-common-mock.ts new file mode 100644 index 000000000..15b99cf0b --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/securefiles-common/securefiles-common-mock.ts @@ -0,0 +1,30 @@ +import * as tl from "azure-pipelines-task-lib/task"; + +export class SecureFileHelpers { + private static fileExtension: string = ".filename"; + + constructor(retryCount?: number) { + tl.debug('Mock SecureFileHelpers constructor'); + + if (retryCount) { + tl.debug('Mock SecureFileHelpers retry count set to: ' + retryCount); + } else { + tl.debug('Mock SecureFileHelpers retry count not set.'); + } + } + + async downloadSecureFile(secureFileId: string) { + tl.debug('Mock downloadSecureFile with id = ' + secureFileId); + const fileName: string = `${secureFileId}${SecureFileHelpers.fileExtension}`; + const tempDownloadPath: string = `/build/temp/${fileName}`; + return tempDownloadPath; + } + + deleteSecureFile(secureFileId: string) { + tl.debug('Mock deleteSecureFile with id = ' + secureFileId); + } + + static setFileExtension(extension: string): void { + this.fileExtension = extension; + } +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/securefiles-common/securefiles-common.ts b/tools/pipelines-tasks/MsixSigning/securefiles-common/securefiles-common.ts new file mode 100644 index 000000000..8241a57ea --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/securefiles-common/securefiles-common.ts @@ -0,0 +1,96 @@ +import * as fs from 'fs'; +import * as Q from 'q'; +import * as tl from 'azure-pipelines-task-lib/task'; +import { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api'; +import { IRequestOptions } from "azure-devops-node-api/interfaces/common/VsoBaseInterfaces"; + + +export class SecureFileHelpers { + serverConnection: WebApi; + + constructor(retryCount?: number) { + const serverUrl: string | undefined = tl.getVariable('System.TeamFoundationCollectionUri'); + if (!serverUrl) { + throw ReferenceError("Argument serverUrl is required but not given."); + } + + const serverCreds: string | undefined = tl.getEndpointAuthorizationParameter('SYSTEMVSSCONNECTION', 'ACCESSTOKEN', false); + console.log("serverCreds:", serverCreds); + + if (!serverCreds) { + throw ReferenceError("Argument serverCreds is required but not given."); + } + + const authHandler = getPersonalAccessTokenHandler(serverCreds); + + const maxRetries = retryCount && retryCount >= 0 ? retryCount : 5; // Default to 5 if not specified + tl.debug('Secure file retry count set to: ' + maxRetries); + const proxy = tl.getHttpProxyConfiguration(); + let options: IRequestOptions = { + allowRetries: true, + maxRetries + }; + + console.log("proxy:", proxy); + + if (proxy) { + options = { ...options, proxy, ignoreSslError: true }; + }; + + this.serverConnection = new WebApi(serverUrl, authHandler, options); + } + + /** + * Download secure file contents to a temporary location for the build + * @param secureFileId + */ + async downloadSecureFile(secureFileId: string): Promise { + const tempDownloadPath: string = this.getSecureFileTempDownloadPath(secureFileId); + tl.debug('Downloading secure file contents to: ' + tempDownloadPath); + const file: NodeJS.WritableStream = fs.createWriteStream(tempDownloadPath); + + const agentApi = await this.serverConnection.getTaskAgentApi(); + + const ticket = tl.getSecureFileTicket(secureFileId); + if (!ticket) { + // Workaround bug #7491. tl.loc only works if the consuming tasks define the resource string. + throw new Error(`Download ticket for SecureFileId ${secureFileId} not found.`); + } + + let fileToDownload: string | undefined = tl.getVariable('SYSTEM.TEAMPROJECT'); + if (!fileToDownload) { + throw ReferenceError("Cannot get file to download"); + } + + const stream = (await agentApi.downloadSecureFile(fileToDownload, secureFileId, ticket, false)).pipe(file); + + const defer = Q.defer(); + stream.on('finish', () => { + defer.resolve(); + }); + await defer.promise; + tl.debug('Downloaded secure file contents to: ' + tempDownloadPath); + return tempDownloadPath; + } + + /** + * Delete secure file from the temporary location for the build + * @param secureFileId + */ + deleteSecureFile(secureFileId: string): void { + const tempDownloadPath: string = this.getSecureFileTempDownloadPath(secureFileId); + if (tl.exist(tempDownloadPath)) { + tl.debug('Deleting secure file at: ' + tempDownloadPath); + tl.rmRF(tempDownloadPath); + } + } + + /** + * Returns the temporary download location for the secure file + * @param secureFileId + */ + getSecureFileTempDownloadPath(secureFileId: string): string { + const fileName: string = tl.getSecureFileName(secureFileId); + return tl.resolve(tl.getVariable('Agent.TempDirectory'), fileName); + } +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/task.json b/tools/pipelines-tasks/MsixSigning/task.json new file mode 100644 index 000000000..c70210bac --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/task.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "501a9528-7fa4-4cb5-b8da-79ded62b74eb", + "name": "MsixSigning", + "friendlyName": "MSIX package signing", + "instanceNameFormat": "Sign MSIX package", + "description": "Sign MSIX packages", + "author": "Microsoft Corporation", + "category": "Package", + "helpUrl": "https://aka.ms/msix-cicd", + "helpMarkDown": "[Learn more about this task](https://aka.ms/msix-cicd)", + "execution": { + "Node10": { + "target": "index.js" + } + }, + "version": { + "Major": 1, + "Minor": 0, + "Patch": 0 + }, + "minimumAgentVersion": "1.95.0", + "inputs": [ + { + "name": "package", + "type": "string", + "label": "Package to sign", + "defaultValue": "$(Build.ArtifactStagingDirectory)\\**\\*.msix*", + "required": true, + "helpMarkDown": "Files to sign. Can be a package or a bundle. All matching files are signed." + }, + { + "name": "certificateType", + "type": "radio", + "label": "Certificate File type", + "defaultValue": "secureFile", + "required": false, + "helpMarkDown": "Source of the certificate file to use. The certificate can come from the [Secure Files library](https://docs.microsoft.com/azure/devops/pipelines/library/secure-files), or be encoded as a string as if fetched with the [Azure Key Vault task](https://docs.microsoft.com/azure/devops/pipelines/tasks/deploy/azure-key-vault)", + "options": { + "secureFile": "Secure file", + "base64": "String-encoded certificate" + } + }, + { + "name": "encodedCertificate", + "type": "string", + "label": "Encoded Certificate File", + "required": true, + "helpMarkDown": "Certificate encoded as a Base64 string.", + "visibleRule": "certificateType == base64" + }, + { + "name": "certificate", + "type": "secureFile", + "label": "Certificate File", + "required": true, + "helpMarkDown": "The file name or GUID of the secure certificate to use for signing. The file will be deleted after the pipeline runs.", + "visibleRule": "certificateType == secureFile" + }, + { + "name": "passwordVariable", + "type": "string", + "label": "Password Variable", + "required": true, + "helpMarkDown": "The name of the variable which stores the password used to access the certificate file for signing. Note that this is NOT the password itself, but rather the variable name, which can be set under Library.", + "visibleRule": "certificateType == secureFile" + }, + { + "name": "timeStampServer", + "type": "string", + "label": "Time Stamp Server", + "required": false, + "helpMarkDown": "A URL that specifies the address of a time stamping server." + } + ] +} diff --git a/tools/pipelines-tasks/MsixSigning/task.loc.json b/tools/pipelines-tasks/MsixSigning/task.loc.json new file mode 100644 index 000000000..b8e7d1154 --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/task.loc.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", + "id": "501a9528-7fa4-4cb5-b8da-79ded62b74eb", + "name": "MsixSigning", + "friendlyName": "ms-resource:loc.friendlyName", + "instanceNameFormat": "ms-resource:loc.instanceNameFormat", + "description": "ms-resource:loc.description", + "author": "Microsoft Corporation", + "category": "Package", + "helpUrl": "https://aka.ms/msix-cicd", + "helpMarkDown": "ms-resource:loc.helpMarkDown", + "execution": { + "Node10": { + "target": "index.js" + } + }, + "version": { + "Major": 1, + "Minor": 0, + "Patch": 0 + }, + "minimumAgentVersion": "1.95.0", + "inputs": [ + { + "name": "package", + "type": "string", + "label": "ms-resource:loc.input.label.package", + "defaultValue": "$(Build.ArtifactStagingDirectory)\\**\\*.msix*", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.package" + }, + { + "name": "certificateType", + "type": "radio", + "label": "ms-resource:loc.input.label.certificateType", + "defaultValue": "secureFile", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.certificateType", + "options": { + "secureFile": "Secure file", + "base64": "String-encoded certificate" + } + }, + { + "name": "encodedCertificate", + "type": "string", + "label": "ms-resource:loc.input.label.encodedCertificate", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.encodedCertificate", + "visibleRule": "certificateType == base64" + }, + { + "name": "certificate", + "type": "secureFile", + "label": "ms-resource:loc.input.label.certificate", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.certificate", + "visibleRule": "certificateType == secureFile" + }, + { + "name": "passwordVariable", + "type": "string", + "label": "ms-resource:loc.input.label.passwordVariable", + "required": true, + "helpMarkDown": "ms-resource:loc.input.help.passwordVariable", + "visibleRule": "certificateType == secureFile" + }, + { + "name": "timeStampServer", + "type": "string", + "label": "ms-resource:loc.input.label.timeStampServer", + "required": false, + "helpMarkDown": "ms-resource:loc.input.help.timeStampServer" + } + ] +} \ No newline at end of file diff --git a/tools/pipelines-tasks/MsixSigning/tsconfig.json b/tools/pipelines-tasks/MsixSigning/tsconfig.json new file mode 100644 index 000000000..3c43903cf --- /dev/null +++ b/tools/pipelines-tasks/MsixSigning/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json" +} diff --git a/tools/pipelines-tasks/PublishForTesting.ps1 b/tools/pipelines-tasks/PublishForTesting.ps1 new file mode 100644 index 000000000..f30ca0d11 --- /dev/null +++ b/tools/pipelines-tasks/PublishForTesting.ps1 @@ -0,0 +1,50 @@ +# Creates the .vsix package and publishes to your test publisher. +param( + [Parameter(Mandatory=$true)] + [string] + $publisherName, + + [Parameter(Mandatory=$true)] + [string] + $extensionId +) + +# Personal Access Token used to access the publisher account. +# This is requested the first time the script is run and is stored encrypted. +$marketplacePatLocation = "$PSScriptRoot\pat.txt" + +function Set-MarketplacePat() +{ + Read-Host -Prompt "PAT" | ConvertTo-SecureString -AsPlainText | ConvertFrom-SecureString | Set-Content -Path $marketplacePatLocation +} + +function Get-MarketplacePat() +{ + if (!(Test-Path $marketplacePatLocation)) + { + Set-MarketplacePat + } + + $securePassword = Get-Content $marketplacePatLocation | ConvertTo-SecureString + + # On Powershell >= 7 + # $securePassword | ConvertFrom-SecureString -AsPlainText + + $credential = New-Object System.Management.Automation.PSCredential ("dummy", $securePassword) + return $credential.GetNetworkCredential().Password +} + +function Update-TaskVersions() +{ + foreach ($path in $(Get-ChildItem -Recurse -Depth 1 -Filter 'task.json')) { + $taskJson = Get-Content $path.FullName -Raw | ConvertFrom-Json + $taskJson.version.Patch += 1 + $taskJson | ConvertTo-Json -Depth 10 | Set-Content $path.FullName + } +} + +Push-Location $taskSrcRoot +Update-TaskVersions +& $PSScriptRoot/build.ps1 Build +tfx extension publish --manifests vss-extension.json --publisher $publisherName --extension-id $extensionId --rev-version --token $(Get-MarketplacePat) +Pop-Location diff --git a/tools/pipelines-tasks/README.md b/tools/pipelines-tasks/README.md new file mode 100644 index 000000000..91e34b08b --- /dev/null +++ b/tools/pipelines-tasks/README.md @@ -0,0 +1,48 @@ +# MSIX ADO Tasks + +This includes the source for Azure Pipelines tasks for: +* Building and packaging MSIX apps. +* Signing MSIX packages. +* Creating VHDX disks for use with MSIX app attach. +* Creating App Installer files. + +## Building + +You need to have Node.js v10 installed (higher versions cause problems with the tests). +When building for the first time, install the development tools (e.g. TypeScript) and dependencies (e.g. node modules) needed with: +``` +.\build.ps1 InstallDevelopmentTools +.\build.ps1 InstallDependencies +``` + +To compile the project: +``` +.\build.ps1 Build +``` +You can also use `tsc`, but the script will also update the localization files if needed. + +To build the common helpers and update the dependencies in other projects: +``` +.\build.ps1 BuildCommonHelpers +``` + +To publish the extension to a private publisher: +``` +.\PublishForTesting.ps1 -publisherName -extensionId +``` +You will need to have a [Personal Access Token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate). +This script is intended to publish to a private publisher to test changes, that is why +you need to set the publisher and use a different name for the extension. +It will auto-increase the patch version of the tasks and the extension. + +To run the tests, from the project root (`/tools/pipelines-tasks/`) do: +``` +mocha +``` + +## Updating dependencies + +* MakeAppx.exe (under `.\common\lib`), SignTool.exe (under `.\MsixSigning\lib`) and related files were taken from the [MSIX Toolkit](https://github.com/microsoft/MSIX-Toolkit/tree/master/Redist.x86) +* msixmgr.exe (under `.\MsixAppAttach\lib`) was taken from the [MSIX Core 1.1. release](https://github.com/microsoft/msix-packaging/releases) +* The MSBuildHelpers at `.\MsixPackaging\MSBuildHelpers` were taken from [azure-pipelines-tasks](https://github.com/microsoft/azure-pipelines-tasks/tree/master/Tasks/Common/MSBuildHelpers). There were only a minor changes to type annotations to make it compile with our configuration. +* vswhere.exe under `.\MsixPackaging\MSBuildHelpers` was taken from [vswhere release 1.0.62](https://github.com/Microsoft/vswhere/releases/download/1.0.62/vswhere.exe) \ No newline at end of file diff --git a/tools/pipelines-tasks/azure-pipelines/build-vsix.yml b/tools/pipelines-tasks/azure-pipelines/build-vsix.yml new file mode 100644 index 000000000..7cf3f1196 --- /dev/null +++ b/tools/pipelines-tasks/azure-pipelines/build-vsix.yml @@ -0,0 +1,115 @@ +# Pipeline to create extension VSIX + +trigger: + branches: + include: + - master + paths: + include: + - tools/pipelines-tasks + +pr: none + +pool: + vmImage: 'windows-latest' + +variables: + tasksRoot: 'tools/pipelines-tasks' + # Version number + major: '1' + minor: '1' + patch: $[counter(variables['minor'], 0)] + +steps: +# Install the tools needed +- task: NodeTool@0 + displayName: 'Use Node 10.x' + inputs: + versionSpec: 10.x + +- task: TfxInstaller@3 + displayName: 'Use Node CLI for Azure DevOps (tfx-cli): v0.7.x' + +# Build the project +- task: PowerShell@2 + displayName: 'Build the project' + inputs: + targetType: filePath + filePath: '$(tasksRoot)/build.ps1' + arguments: BuildForProduction + +# Run the tests +- task: NuGetCommand@2 + displayName: 'Restore NuGet packages for test project' + inputs: + restoreSolution: '$(tasksRoot)/test/assets/HelloWorldUWPApp/HelloWorldApp.sln' + +- powershell: | + npx mocha + displayName: 'Run the tests' + workingDirectory: '$(tasksRoot)' + env: + SYSTEM_CULTURE: 'en-US' + +# Package and sign the VSIX +- task: PackageAzureDevOpsExtension@3 + displayName: 'Package Extension: $(tasksRoot)' + inputs: + rootFolder: '$(tasksRoot)' + patternManifest: 'vss-extension.json' + outputPath: '$(Build.ArtifactStagingDirectory)\MsixPackagingExtension.vsix' + publisherId: 'MSIX' + extensionVersion: '$(major).$(minor).$(patch)' + extensionVisibility: public + updateTasksVersion: true + +- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + +- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + displayName: 'ESRP CodeSigning' + inputs: + ConnectedServiceName: 'ESRP CodeSigning' + FolderPath: '$(Build.ArtifactStagingDirectory)' + Pattern: MsixPackagingExtension.vsix + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "KeyCode" : "CP-233016", + "OperationCode" : "OpcSign", + "Parameters" : { + "FileDigest" : "/fd SHA256" + }, + "ToolName" : "sign", + "ToolVersion" : "1.0" + }, + { + "KeyCode" : "CP-233016", + "OperationCode" : "OpcVerify", + "Parameters" : {}, + "ToolName" : "sign", + "ToolVersion" : "1.0" + } + ] + +- task: PublishPipelineArtifact@1 + displayName: 'Publish VSIX artifact' + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)\MsixPackagingExtension.vsix' + artifact: 'VSIX' + publishLocation: 'pipeline' + +# Publish privately +# Use different task IDs from public extension to prevent clashing. +- task: PublishAzureDevOpsExtension@3 + displayName: 'Publish Extension' + inputs: + connectedServiceName: 'Visual Studio Marketplace - MSIX' + fileType: vsix + vsixFile: '$(Build.ArtifactStagingDirectory)\MsixPackagingExtension.vsix' + extensionId: 'msix-ci-automation-task-dev' + extensionName: 'MSIX Packaging (Preview)' + extensionVersion: '$(major).$(minor).$(patch)' + updateTasksId: true + extensionVisibility: 'privatepreview' diff --git a/tools/pipelines-tasks/azure-pipelines/ci-build.yml b/tools/pipelines-tasks/azure-pipelines/ci-build.yml new file mode 100644 index 000000000..2ccbf5a2b --- /dev/null +++ b/tools/pipelines-tasks/azure-pipelines/ci-build.yml @@ -0,0 +1,49 @@ +# Build and test pipeline for CI + +# Branches that trigger a build on commit +trigger: +- master +- release_v* + +# Branches that trigger builds on PR +pr: + branches: + include: + - master + - release_v* + paths: + include: + - tools/pipelines-tasks + +pool: + vmImage: 'windows-latest' + +variables: + tasksRoot: 'tools/pipelines-tasks' + +steps: + +- task: NodeTool@0 + displayName: 'Use Node 10.x' + inputs: + versionSpec: 10.x + +# Build the project +- task: PowerShell@2 + displayName: 'Build the project' + inputs: + filePath: '$(tasksRoot)/build.ps1' + arguments: 'BuildForProduction' + +# Run the tests +- task: NuGetCommand@2 + displayName: 'Restore NuGet packages for test project' + inputs: + restoreSolution: '$(tasksRoot)/test/assets/HelloWorldUWPApp/HelloWorldApp.sln' + +- powershell: | + npx mocha + displayName: 'Run the tests' + workingDirectory: '$(tasksRoot)' + env: + SYSTEM_CULTURE: 'en-US' diff --git a/tools/pipelines-tasks/azure-pipelines/localization.yml b/tools/pipelines-tasks/azure-pipelines/localization.yml new file mode 100644 index 000000000..3b710047c --- /dev/null +++ b/tools/pipelines-tasks/azure-pipelines/localization.yml @@ -0,0 +1,69 @@ +# +# Localization +# This pipeline uploads English strings files to the localization service, downloads any translated +# files which are available, and checks them in to git. This pipeline relies on Microsoft-internal +# resources to run. +# + +# Expects a variable called LocServiceKey to contain the OAuth client secret for Touchdown. + +trigger: none +pr: none + +name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) + +pool: + vmImage: windows-latest + +variables: + tasksRoot: 'tools/pipelines-tasks' + skipComponentGovernanceDetection: true + +steps: +- task: TouchdownBuildTask@1 + displayName: Send resources to Touchdown Build + inputs: + teamId: 15687 + authId: 2796a411-f030-46c1-ae3e-ab56f60ea523 + authKey: $(LocServiceKey) + isPreview: false + relativePathRoot: '$(tasksRoot)' + resourceFilePath: '*\Strings\resources.resjson\en-US\resources.resjson' + appendRelativeDir: true + outputDirectoryRoot: '$(tasksRoot)' + +# TouchdownBuildTask will place the localized files under +# Strings\resources.resjson\en-US\ +# We need to move them one level up +# E.g. move Strings\resources.resjson\en-US\fr-FR to make it Strings\resources.resjson\fr-FR +- powershell: | + # Repeat for each strings directory in all tasks + Get-ChildItem -Recurse -Depth 1 -Filter Strings | %{ + $resourcesDir = Join-Path $_.FullName Resources.resjson + + # Remove existing localized files + Get-ChildItem $resourcesDir -Exclude en-US | Remove-Item -Recurse + + # Find the misplaced localized files and move them to the right place + Get-ChildItem $resourcesDir\en-US -Exclude resources.resjson | %{ + Move-Item $_ $resourcesDir + } + } + displayName: Place loc files in the right location + workingDirectory: '$(tasksRoot)' + +- script: | + cd $(Build.SourcesDirectory) + git add -A + git diff --cached --exit-code + echo ##vso[task.setvariable variable=hasChanges]%errorlevel% + git diff --cached > $(Build.ArtifactStagingDirectory)\LocalizedStrings.patch + displayName: Check for changes and create patch file + workingDirectory: '$(tasksRoot)' + +- task: PublishPipelineArtifact@0 + displayName: Publish patch file as artifact + condition: eq(variables['hasChanges'], '1') + inputs: + artifactName: Patch + targetPath: $(Build.ArtifactStagingDirectory) \ No newline at end of file diff --git a/tools/pipelines-tasks/azure-pipelines/sample-pipeline.yml b/tools/pipelines-tasks/azure-pipelines/sample-pipeline.yml new file mode 100644 index 000000000..017f14fc6 --- /dev/null +++ b/tools/pipelines-tasks/azure-pipelines/sample-pipeline.yml @@ -0,0 +1,58 @@ +# A sample pipeline using the tasks. + +# The pipeline expects to have a secure file named certificate.pfx +# and a secret variable called Password. + +variables: + tasksRoot: 'tools/pipelines-tasks' + solution: '$(tasksRoot)/test/assets/HelloWorldUWPApp/HelloWorldApp.sln' + manifest: '$(tasksRoot)/test/assets/HelloWorldUWPApp/Package.appxmanifest' + platform: 'x86' + package: '$(Build.ArtifactStagingDirectory)/package.msix' + +pool: + vmImage: 'windows-latest' + +steps: +- task: NuGetCommand@2 + displayName: 'NuGet restore' + inputs: + restoreSolution: $(solution) + +# Build the package +- task: MsixPackaging@0 + inputs: + outputPath: $(package) + solution: $(solution) + clean: true + buildConfiguration: release + buildPlatform: $(platform) + updateAppVersion: true + manifestFile: $(manifest) + appVersion: '1.2.3.4' + +- task: MsixSigning@0 + inputs: + package: $(package) + certificate: 'certificate.pfx' + passwordVariable: 'Password' + +- task: MsixAppAttach@0 + inputs: + package: $(package) + vhdxOutputPath: '$(Build.ArtifactStagingDirectory)/App.vhdx' + vhdxSize: '5' + +- task: AppInstallerFile@0 + inputs: + package: $(package) + outputPath: '$(Build.ArtifactStagingDirectory)/App.appinstaller' + method: 'create' + fileVersion: '1.0.0.0' + uri: 'https://example.com/AppInstallerFile' + mainItemUri: 'https://example.com/App' + +- task: PublishPipelineArtifact@1 + displayName: 'Publish Pipeline Artifact' + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)' diff --git a/tools/pipelines-tasks/build.ps1 b/tools/pipelines-tasks/build.ps1 new file mode 100644 index 000000000..391a437fb --- /dev/null +++ b/tools/pipelines-tasks/build.ps1 @@ -0,0 +1,223 @@ +[CmdletBinding()] +param ( + [string] + [ValidateSet( + 'InstallDevelopmentTools', + 'InstallDependencies', + 'BuildCommonHelpers', + 'Build', + 'BuildForProduction' + )] + $action +) + +$taskNames = ( + "AppInstallerFile", + "MsixAppAttach", + "MsixPackaging", + "MsixSigning" +) + +# npm likes writing to the stderr, which PS will sometimes interpret as an error. +# This wrapper lets us call it while temporarily disabling this error handling. +function npm +{ + $oldErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'SilentlyContinue' + npm.cmd $args + $ErrorActionPreference = $oldErrorActionPreference + + if ($LASTEXITCODE -ne 0) + { + throw "npm failed" + } +} + +function OnDirectory([string]$dir, [scriptblock]$script, [object[]]$arguments) +{ + Push-Location $dir + try + { + Invoke-Command $script -ArgumentList $arguments + } + finally + { + Pop-Location + } +} + +function OnEachTask([string]$message, [scriptblock]$script) +{ + foreach ($task in $taskNames) + { + Write-Host "$task : $message" + OnDirectory "$PSScriptRoot\$task" $script + } +} + +# Builds the common helpers and installs it to each task. +# This needs to run after any changes to the common helpers. +function BuildCommonHelpers([switch]$installDependencies) +{ + OnDirectory "$PSScriptRoot\common" -arguments $installDependencies { + param($installDependencies) + + if ($installDependencies) + { + Write-Host "Installing dependencies" + npm ci + } + + # Build the directory + Write-Host "Compiling common helpers" + npx tsc + if (-not $?) + { + throw "Failed to build 'common'" + } + + # Pack the package for npm. + # We need to reference a .tgz package instead of a directory because + # tfx cannot deal with symbolic links. + # + # I.e. if we reference the package in the task's package.json as + # "dependencies": { + # "common": "../common" + # } + # then the task's node_modules/common will be a symbolic link to + # /common, and creating the extension with tfx will fail. + # + # Instead we reference the packaged common as + # "dependencies": { + # "common": "../common/msix-tasks-helpers-1.0.0.tgz" + # } + # This will copy and extract the package into the task's + # node_modules/. + # + # The downside of this is that it changes to common/ are not + # picked up by tasks, hence this function. + + # Before packaging, remove existing packages so they are not + # included in new package. + Remove-Item *.tgz + + Write-Host "Packaging common helpers" + npm pack + + # Install the package to all the tasks. + # The helpers need to be installed to each task because each task is + # installed independently to the agent, so only files on the task's + # directory can be used. + OnEachTask "Installing common helpers" { + npm install common + } + } +} + +# Installs the development tools required to work on the repo. +# This needs Node.js v10 to already be installed, and will only install the +# required global Node modules +function InstallDevelopmentTools() +{ + # Typescript compiler + npm install -g typescript + + # CLI tools to interact with Azure DevOps (e.g. build and publish the extension) + npm install -g tfx-cli + + # Test platform + npm install -g mocha +} + +# Installs the dependencies for every project (the common helpers and all tasks) +function InstallAllDepenencies() +{ + # Always prefer 'npm ci' over 'npm install' to use specific package versions + # from package-lock.json and get the same results on any machine. + + # First build the common helpers as they are a dependency of everything else. + BuildCommonHelpers -installDependencies + + # Install dependencies for top level scripts. + OnDirectory $PSScriptRoot { + npm ci + } + + # Install dependencies for each task. + # This will install the common helpers again, but it's not too much extra work. + OnEachTask "Install dependencies" { + npm ci + } + + OnDirectory $PSScriptRoot\MsixPackaging { + if (-not (Test-Path ps_modules/VstsTaskSdk)) + { + Write-Host "Installing VSTS Task SDK Powershell Module" + New-Item -Type Directory ps_modules -ErrorAction SilentlyContinue + Save-Module -Name VstsTaskSdk -Path .\ps_modules\ + } + } + + # Create a test certificate for local testing/debugging. + # This creates the file expected by the tests and debug inputs. + # Create the certificate + $cert = New-SelfSignedCertificate -Type Custom ` + -Subject "CN=HelloWorldPublisher" ` + -KeyUsage DigitalSignature ` + -FriendlyName "HelloWorldPublisher" ` + -CertStoreLocation "Cert:\CurrentUser\My" ` + -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") + + # Export it as a .pfx + $password = ConvertTo-SecureString -String password -Force -AsPlainText + Export-PfxCertificate -Cert $cert -FilePath $PSScriptRoot\test\assets\certificate.pfx -Password $password + + # Write it as a base64 string + $certBytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx) + [System.Convert]::ToBase64String($certBytes) | Out-File $PSScriptRoot\test\assets\certificate.txt -Encoding utf8 + + # Remove the certificate from the cert store + $certPath = "Cert:\CurrentUser\My\$($cert.Thumbprint)" + Remove-Item $certPath +} + +# Build all the tasks and update the loc strings. +function Build() { + Write-Host "Build all directories and update loc files" + OnDirectory $PSScriptRoot { + npx tsc + node create-resjson.js + } +} + +# Build all the tasks and removes development dependencies. +# This doesn't create the extension .vsix package. +function BuildForProduction() +{ + InstallDevelopmentTools + InstallAllDepenencies + Build + + # Fail if loc files changed. + # This is to cause the pipelines to fail if the changes were not commited. + # Ignore changes to package-lock.json as it will contain a new hash for the common helpers. + $filesModified = git status --porcelain | Where-Object { $_ -notmatch 'package-lock.json' } + if ($filesModified) + { + $filesModified + throw "There are uncommited changes to loc strings" + } + + OnEachTask "Remove development dependencies" { + npm prune + } +} + +switch ($action) +{ + 'InstallDevelopmentTools' { InstallDevelopmentTools } + 'InstallDependencies' { InstallAllDepenencies } + 'BuildCommonHelpers' { BuildCommonHelpers } + 'Build' {Build} + 'BuildForProduction' { BuildForProduction } +} \ No newline at end of file diff --git a/tools/pipelines-tasks/common/helpers.ts b/tools/pipelines-tasks/common/helpers.ts new file mode 100644 index 000000000..723e8e57c --- /dev/null +++ b/tools/pipelines-tasks/common/helpers.ts @@ -0,0 +1,119 @@ +import fs = require('fs'); +import os = require('os'); +import path = require('path'); +import tl = require('azure-pipelines-task-lib/task'); +import xml = require('xml2js'); + +import { ToolRunner } from 'azure-pipelines-task-lib/toolrunner'; + +export const MAKEAPPX_PATH = path.join(__dirname, 'lib', 'makeappx'); + +/** + * When running on an agent, returns the value of Agent.TempDirectory which is cleaned after + * each pipeline job. When running locally, returns the local temp directory. + */ +export const getTempDirectory = (): string => +{ + return tl.getVariable('Agent.TempDirectory') ?? os.tmpdir(); +} + +/** + * Gets an input variable, checking that it is present and throwing if it's not. + * @param variableName Name of the input variable to get. + * @param errorMessage Message to include in the error if the variable is empty, null or undefined. + * @returns The value of the input variable if it is set. + */ +export const getInputWithErrorCheck = (variableName: string, errorMessage: string): string => +{ + const variableValue: string | undefined = tl.getInput(variableName); + if (!variableValue) + { + throw Error('Input Error: ' + errorMessage); + } + + return variableValue; +} + +/** + * Gets a ToolRunner for running a Powershell script. + * Script arguments can be added later by the caller. + * @param scriptPath Script to run. + */ +export const getPowershellRunner = (scriptPath: string): ToolRunner => +{ + const powershellRunner: ToolRunner = tl.tool('powershell') + .arg('-NoLogo') + .arg('-NoProfile') + .arg('-NonInteractive') + .arg(['-ExecutionPolicy', 'Unrestricted']); + + // Quote the script path to allow for spaces. + // Existing quotes need to be escaped. + powershellRunner.arg(`& '${scriptPath.replace("'", "''")}'`); + return powershellRunner; +} + +/** + * Increment the current version given by the method specified. A version + * is in the form of (major).(minor).(build).(revision). The incremental + * method specifies which part of the version to increment. For example, + * if the current version given is 1.0.0.0 and the incrementalMethod = 'revision', + * then '1.0.0.1' is returned. + * @param currentVersion + * @param incrementalMethod + */ +export const incrementVersion = (currentVersion: string[], incrementalMethod: string): string => +{ + const methodToIndex: { [method: string]: number } = + { + 'revision': 3, + 'build': 2, + 'minor': 1, + 'major': 0 + } + + if (!Object.keys(methodToIndex).includes(incrementalMethod)) + { + throw Error(`"${incrementalMethod}" is not a valid method to increment the version.`); + } + + // TODO: roll over if maximum reached? + const index: number = methodToIndex[incrementalMethod]; + currentVersion[index] = (parseInt(currentVersion[index]) + 1).toString(); + for (let a: number = index + 1; a < 4; a++) + { + currentVersion[a] = '0'; + } + + return currentVersion.join('.') +} + +/** + * Unpack/unbundle a package or bundle and put the outputs into . + * @param filePath the path to the file which we wish to unpack/unbundle + * @param outputDirectory the directory where the content of the unpacked file will be deposited to + * @param isBundle whether or not the given file is a bundle + */ +export const unpackFile = async (filePath: string, outputDirectory: string, isBundle: boolean) => +{ + const makeAppxRunner: ToolRunner = tl.tool(MAKEAPPX_PATH); + makeAppxRunner.arg(isBundle ? 'unbundle' : 'unpack'); + makeAppxRunner.arg(['-p', filePath]); + makeAppxRunner.arg(['-d', outputDirectory]); + makeAppxRunner.arg('-o'); + + await makeAppxRunner.exec(); +} + +export const parseXml = async (filePath: string): Promise => +{ + const fileText: string = fs.readFileSync(filePath).toString(); + const parser = new xml.Parser(); + return await parser.parseStringPromise(fileText); +} + +export const writeXml = (xmlObject: any, filePath: string) => +{ + const xmlBuilder = new xml.Builder(); + fs.writeFileSync(filePath, xmlBuilder.buildObject(xmlObject)); +} \ No newline at end of file diff --git a/tools/pipelines-tasks/common/lib/AppxPackaging.dll.mui b/tools/pipelines-tasks/common/lib/AppxPackaging.dll.mui new file mode 100644 index 000000000..b1031bec4 Binary files /dev/null and b/tools/pipelines-tasks/common/lib/AppxPackaging.dll.mui differ diff --git a/tools/pipelines-tasks/common/lib/Microsoft.Windows.Build.Appx.AppxPackaging.dll.manifest b/tools/pipelines-tasks/common/lib/Microsoft.Windows.Build.Appx.AppxPackaging.dll.manifest new file mode 100644 index 000000000..becc73ae7 --- /dev/null +++ b/tools/pipelines-tasks/common/lib/Microsoft.Windows.Build.Appx.AppxPackaging.dll.manifest @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/pipelines-tasks/common/lib/Microsoft.Windows.Build.Appx.AppxSip.dll.manifest b/tools/pipelines-tasks/common/lib/Microsoft.Windows.Build.Appx.AppxSip.dll.manifest new file mode 100644 index 000000000..111d35ec2 --- /dev/null +++ b/tools/pipelines-tasks/common/lib/Microsoft.Windows.Build.Appx.AppxSip.dll.manifest @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/tools/pipelines-tasks/common/lib/Microsoft.Windows.Build.Appx.OpcServices.dll.manifest b/tools/pipelines-tasks/common/lib/Microsoft.Windows.Build.Appx.OpcServices.dll.manifest new file mode 100644 index 000000000..b17e53d11 --- /dev/null +++ b/tools/pipelines-tasks/common/lib/Microsoft.Windows.Build.Appx.OpcServices.dll.manifest @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/tools/pipelines-tasks/common/lib/appxpackaging.dll b/tools/pipelines-tasks/common/lib/appxpackaging.dll new file mode 100644 index 000000000..44de676e1 Binary files /dev/null and b/tools/pipelines-tasks/common/lib/appxpackaging.dll differ diff --git a/tools/pipelines-tasks/common/lib/appxsip.dll b/tools/pipelines-tasks/common/lib/appxsip.dll new file mode 100644 index 000000000..e40d552f6 Binary files /dev/null and b/tools/pipelines-tasks/common/lib/appxsip.dll differ diff --git a/tools/pipelines-tasks/common/lib/makeappx.exe b/tools/pipelines-tasks/common/lib/makeappx.exe new file mode 100644 index 000000000..dee887765 Binary files /dev/null and b/tools/pipelines-tasks/common/lib/makeappx.exe differ diff --git a/tools/pipelines-tasks/common/lib/makepri.exe b/tools/pipelines-tasks/common/lib/makepri.exe new file mode 100644 index 000000000..16395d3e6 Binary files /dev/null and b/tools/pipelines-tasks/common/lib/makepri.exe differ diff --git a/tools/pipelines-tasks/common/lib/opcservices.dll b/tools/pipelines-tasks/common/lib/opcservices.dll new file mode 100644 index 000000000..a806283fa Binary files /dev/null and b/tools/pipelines-tasks/common/lib/opcservices.dll differ diff --git a/tools/pipelines-tasks/common/package-lock.json b/tools/pipelines-tasks/common/package-lock.json new file mode 100644 index 000000000..bc4fe8f06 --- /dev/null +++ b/tools/pipelines-tasks/common/package-lock.json @@ -0,0 +1,517 @@ +{ + "name": "msix-tasks-helpers", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-OU2+C7X+5Gs42JZzXoto7yOQ0A0=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/form-data": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", + "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/mocha": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz", + "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", + "dev": true + }, + "@types/node": { + "version": "14.14.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz", + "integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw==" + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "dev": true + }, + "@types/qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==", + "dev": true + }, + "@types/xml2js": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.8.tgz", + "integrity": "sha512-EyvT83ezOdec7BhDaEcsklWy7RSIdi6CNe95tmOAK0yx/Lm30C9K75snT3fYayK59ApC2oyW+rcHErdG05FHJA==", + "requires": { + "@types/node": "*" + } + }, + "adm-zip": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "azure-devops-node-api": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-10.2.2.tgz", + "integrity": "sha512-4TVv2X7oNStT0vLaEfExmy3J4/CzfuXolEcQl/BRUmvGySqKStTG2O55/hUQ0kM7UJlZBLgniM0SBq4d/WkKow==", + "requires": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "azure-pipelines-task-lib": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.12.2.tgz", + "integrity": "sha512-ofAdVZcL90Qv6zYcKa1vK3Wnrl2kxoKX/Idvb7RWrqHQzcJlAEjCU4UCB5y6NnSKqRSyVTIhdS6hChphpOaiMQ==", + "requires": { + "minimatch": "3.0.4", + "mockery": "^1.7.0", + "q": "^1.1.2", + "semver": "^5.1.0", + "shelljs": "^0.3.0", + "sync-request": "3.0.1", + "uuid": "^3.0.1" + }, + "dependencies": { + "sync-request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-3.0.1.tgz", + "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", + "requires": { + "concat-stream": "^1.4.7", + "http-response-object": "^1.0.1", + "then-request": "^2.0.1" + } + } + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "http-basic": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-2.5.1.tgz", + "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.6", + "http-response-object": "^1.0.0" + } + }, + "http-response-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", + "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "mime-db": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", + "dev": true + }, + "mime-types": { + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", + "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "dev": true, + "requires": { + "mime-db": "1.47.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mockery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", + "integrity": "sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8=" + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" + }, + "parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "sync-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", + "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", + "dev": true, + "requires": { + "http-response-object": "^3.0.1", + "sync-rpc": "^1.2.1", + "then-request": "^6.0.0" + }, + "dependencies": { + "@types/node": { + "version": "10.17.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.57.tgz", + "integrity": "sha512-9ejqfD/nkpl2RTUByUnkhE1xQFw6NWBE/CVsMuKnUvHRGm+HKFvSdHoyuJqKpG/N0hX7i3QHuf+OddN5WIHxMQ==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "http-basic": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", + "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "dev": true, + "requires": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + } + }, + "http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dev": true, + "requires": { + "@types/node": "^10.0.3" + } + }, + "promise": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", + "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", + "dev": true, + "requires": { + "asap": "~2.0.6" + } + }, + "then-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", + "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "dev": true, + "requires": { + "@types/concat-stream": "^1.6.0", + "@types/form-data": "0.0.33", + "@types/node": "^8.0.0", + "@types/qs": "^6.2.31", + "caseless": "~0.12.0", + "concat-stream": "^1.6.0", + "form-data": "^2.2.0", + "http-basic": "^8.1.1", + "http-response-object": "^3.0.1", + "promise": "^8.0.0", + "qs": "^6.4.0" + }, + "dependencies": { + "@types/node": { + "version": "8.10.66", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", + "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", + "dev": true + } + } + } + } + }, + "sync-rpc": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", + "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "dev": true, + "requires": { + "get-port": "^3.1.0" + } + }, + "then-request": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", + "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.7", + "http-basic": "^2.5.1", + "http-response-object": "^1.1.0", + "promise": "^7.1.1", + "qs": "^6.1.0" + } + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "typed-rest-client": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", + "integrity": "sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg==", + "requires": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "underscore": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.0.tgz", + "integrity": "sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + } + } +} diff --git a/tools/pipelines-tasks/common/package.json b/tools/pipelines-tasks/common/package.json new file mode 100644 index 000000000..91a76256d --- /dev/null +++ b/tools/pipelines-tasks/common/package.json @@ -0,0 +1,25 @@ +{ + "name": "msix-tasks-helpers", + "version": "1.0.0", + "description": "Common helpers for the MSIX packaging tasks", + "repository": { + "type": "git", + "url": "git+github.com/microsoft/msix-ado-tasks-extension.git" + }, + "scripts": {}, + "author": "Microsoft Coporation", + "license": "MIT", + "dependencies": { + "@types/xml2js": "^0.4.8", + "adm-zip": "^0.5.5", + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "xml2js": "^0.4.23" + }, + "devDependencies": { + "@types/mocha": "^7.0.2", + "@types/node": "^14.14.39", + "@types/q": "^1.5.4", + "sync-request": "^6.1.0" + } +} diff --git a/tools/pipelines-tasks/common/tsconfig.json b/tools/pipelines-tasks/common/tsconfig.json new file mode 100644 index 000000000..3c43903cf --- /dev/null +++ b/tools/pipelines-tasks/common/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json" +} diff --git a/tools/pipelines-tasks/create-resjson.ts b/tools/pipelines-tasks/create-resjson.ts new file mode 100644 index 000000000..d7219cea0 --- /dev/null +++ b/tools/pipelines-tasks/create-resjson.ts @@ -0,0 +1,148 @@ +// Adapted from github.com/microsoft/azure-pipelines-tasks/ +import fs = require('fs'); +import os = require('os'); +import path = require('path'); +import shell = require('shelljs'); + +const taskNames: string[] = +[ + 'AppInstallerFile', + 'MsixAppAttach', + 'MsixPackaging', + 'MsixSigning', +] + +const shellAssert = () => { + var errMsg = shell.error(); + if (errMsg) { + throw new Error(errMsg); + } +}; + +const mkdir = (options: string, target: string) => { + if (target) { + shell.mkdir(options, target); + } + else { + shell.mkdir(options); + } + + shellAssert(); +} + +const fileToJson = (file: string) => { + var jsonFromFile = JSON.parse(fs.readFileSync(file).toString()); + return jsonFromFile; +} + +const createResjson = (task: any, taskPath: string) => { + const resources: any = {}; + if (task.hasOwnProperty('friendlyName')) { + resources['loc.friendlyName'] = task.friendlyName; + } + + if (task.hasOwnProperty('helpMarkDown')) { + resources['loc.helpMarkDown'] = task.helpMarkDown; + } + + if (task.hasOwnProperty('description')) { + resources['loc.description'] = task.description; + } + + if (task.hasOwnProperty('instanceNameFormat')) { + resources['loc.instanceNameFormat'] = task.instanceNameFormat; + } + + if (task.hasOwnProperty('releaseNotes')) { + resources['loc.releaseNotes'] = task.releaseNotes; + } + + if (task.hasOwnProperty('groups')) { + task.groups.forEach(function (group: any) { + if (group.hasOwnProperty('name')) { + resources['loc.group.displayName.' + group.name] = group.displayName; + } + }); + } + + if (task.hasOwnProperty('inputs')) { + task.inputs.forEach(function (input: any) { + if (input.hasOwnProperty('name')) { + resources['loc.input.label.' + input.name] = input.label; + + if (input.hasOwnProperty('helpMarkDown') && input.helpMarkDown) { + resources['loc.input.help.' + input.name] = input.helpMarkDown; + } + } + }); + } + + if (task.hasOwnProperty('messages')) { + Object.keys(task.messages).forEach(function (key: any) { + resources['loc.messages.' + key] = task.messages[key]; + }); + } + + var resjsonPath = path.join(taskPath, 'Strings', 'resources.resjson', 'en-US', 'resources.resjson'); + mkdir('-p', path.dirname(resjsonPath)); + var resjsonContent = JSON.stringify(resources, null, /* space */ 2); + if (process.platform == 'win32') { + resjsonContent = resjsonContent.replace(/\n/g, os.EOL); + } + fs.writeFileSync(resjsonPath, resjsonContent); +}; + +const createTaskLocJson = (taskPath: string) => { + const taskJsonPath = path.join(taskPath, 'task.json'); + const taskLoc: any = fileToJson(taskJsonPath); + taskLoc.friendlyName = 'ms-resource:loc.friendlyName'; + taskLoc.helpMarkDown = 'ms-resource:loc.helpMarkDown'; + taskLoc.description = 'ms-resource:loc.description'; + taskLoc.instanceNameFormat = 'ms-resource:loc.instanceNameFormat'; + if (taskLoc.hasOwnProperty('releaseNotes')) { + taskLoc.releaseNotes = 'ms-resource:loc.releaseNotes'; + } + + if (taskLoc.hasOwnProperty('groups')) { + taskLoc.groups.forEach(function (group: any) { + if (group.hasOwnProperty('name')) { + group.displayName = 'ms-resource:loc.group.displayName.' + group.name; + } + }); + } + + if (taskLoc.hasOwnProperty('inputs')) { + taskLoc.inputs.forEach(function (input: any) { + if (input.hasOwnProperty('name')) { + input.label = 'ms-resource:loc.input.label.' + input.name; + + if (input.hasOwnProperty('helpMarkDown') && input.helpMarkDown) { + input.helpMarkDown = 'ms-resource:loc.input.help.' + input.name; + } + } + }); + } + + if (taskLoc.hasOwnProperty('messages')) { + Object.keys(taskLoc.messages).forEach(function (key) { + taskLoc.messages[key] = 'ms-resource:loc.messages.' + key; + }); + } + + var taskLocContent = JSON.stringify(taskLoc, null, 2); + if (process.platform == 'win32') { + taskLocContent = taskLocContent.replace(/\n/g, os.EOL); + } + fs.writeFileSync(path.join(taskPath, 'task.loc.json'), taskLocContent); +}; + +for (const taskName of taskNames) +{ + const taskPath = path.join(__dirname, taskName); + const taskJsonPath = path.join(taskPath, 'task.json'); + const taskJson = fileToJson(taskJsonPath); + + // create loc files + createTaskLocJson(taskPath); + createResjson(taskJson, taskPath); +} \ No newline at end of file diff --git a/tools/pipelines-tasks/images/extension-icon.png b/tools/pipelines-tasks/images/extension-icon.png new file mode 100644 index 000000000..052291f00 Binary files /dev/null and b/tools/pipelines-tasks/images/extension-icon.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/add-task.png b/tools/pipelines-tasks/images/msix-packaging-ext/add-task.png new file mode 100644 index 000000000..079781039 Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/add-task.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/agent-specification.png b/tools/pipelines-tasks/images/msix-packaging-ext/agent-specification.png new file mode 100644 index 000000000..b49c9a963 Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/agent-specification.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/app-attach.png b/tools/pipelines-tasks/images/msix-packaging-ext/app-attach.png new file mode 100644 index 000000000..631edd6f8 Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/app-attach.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/app-installer.png b/tools/pipelines-tasks/images/msix-packaging-ext/app-installer.png new file mode 100644 index 000000000..c1040a6e9 Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/app-installer.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/browse-marketplace.png b/tools/pipelines-tasks/images/msix-packaging-ext/browse-marketplace.png new file mode 100644 index 000000000..23a53f8c3 Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/browse-marketplace.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/build-and-package.png b/tools/pipelines-tasks/images/msix-packaging-ext/build-and-package.png new file mode 100644 index 000000000..ee33fde06 Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/build-and-package.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/classic-editor.png b/tools/pipelines-tasks/images/msix-packaging-ext/classic-editor.png new file mode 100644 index 000000000..4ee4a64bd Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/classic-editor.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/empty-job.png b/tools/pipelines-tasks/images/msix-packaging-ext/empty-job.png new file mode 100644 index 000000000..7e3ef766b Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/empty-job.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/new-pipeline.png b/tools/pipelines-tasks/images/msix-packaging-ext/new-pipeline.png new file mode 100644 index 000000000..201dcf1eb Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/new-pipeline.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/select-pipeline.png b/tools/pipelines-tasks/images/msix-packaging-ext/select-pipeline.png new file mode 100644 index 000000000..8ced78ad7 Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/select-pipeline.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/signing.png b/tools/pipelines-tasks/images/msix-packaging-ext/signing.png new file mode 100644 index 000000000..498bfc27b Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/signing.png differ diff --git a/tools/pipelines-tasks/images/msix-packaging-ext/vcs.png b/tools/pipelines-tasks/images/msix-packaging-ext/vcs.png new file mode 100644 index 000000000..aaebdc589 Binary files /dev/null and b/tools/pipelines-tasks/images/msix-packaging-ext/vcs.png differ diff --git a/tools/pipelines-tasks/overview.md b/tools/pipelines-tasks/overview.md new file mode 100644 index 000000000..6354d6771 --- /dev/null +++ b/tools/pipelines-tasks/overview.md @@ -0,0 +1,106 @@ +# MSIX Packaging Extension + +The *MSIX Packaging* Extension is an Azure DevOps extension which helps build, package and sign Windows apps using the MSIX package format. + +CI/CD workflows have become an integral part of the development process to improve efficiency and quality while reducing cost and time to market. Microsoft's CI/CD solution Azure DevOps Pipelines is widely adopted and popular, but the current process of integrating build and deployment workflows for apps that need to be packaged as MSIX into Azure Pipelines by using YAML files is tedious, specifically for people that are not Azure Pipelines or MSIX experts. This Azure DevOps extension offers a straightforward, intuitive and UI based solution making it easier to automate build and deployment process for apps being packaged as MSIX, and for apps with existing CI/CD workflows to move to MSIX without disrupting their build and deployment mechanisms. + +The *MSIX Packaging* Extension contains the following tasks that you can use to custom build your pipeline according to your requirements: + +1. **MSIX build and package** - to build and package Windows apps using the MSIX package format +2. **MSIX package signing** - to sign MSIX packages using a trusted certificate +3. **App installer file for MSIX** - to create or update a .appinstaller file for MSIX apps +4. **Create package for MSIX app attach** - to create a VHDX package for MSIX app attach + +## Install the extension + +Browse the Azure DevOps Marketplace and look for the extension name *MSIX Packaging* Extension. + +![Browse the marketplace](images/msix-packaging-ext/browse-marketplace.png) + +## Create a Pipeline + +Create a new pipeline for your Azure DevOps project. + +![select pipeline](images/msix-packaging-ext/select-pipeline.png) + +![New pipeline](images/msix-packaging-ext/new-pipeline.png) + +Select the option to *Use the classic editor to create a pipeline without YAML*. + +![Use the classic editor](images/msix-packaging-ext/classic-editor.png) + +Select your version control system and provide your repository and default branch details. + +![Configure source vcs](images/msix-packaging-ext/vcs.png) + +When asked to *Select a template*, click *start with an **Empty job***. + +![Start with an empty job](images/msix-packaging-ext/empty-job.png) + +Change your *Agent Specification* selection to ***windows-2019*** since the MSIX extension runs only on a Windows agent. + +![Agent specification windows](images/msix-packaging-ext/agent-specification.png) + +You should see *Agent job 1* by default in your pipeline. Click on the plus symbol to *Add a task to Agent job 1*. + +Search for ***MSIX*** in the *Add tasks* search bar and you should see the tasks mentioned before in the *MSIX Packaging* Extension. You can custom build your pipeline by adding the tasks you need according to your requirements. But we will demonstrate how to configure all four tasks on this page. + +![Add a task](images/msix-packaging-ext/add-task.png) + +### MSIX build and package + +![Build and package task](images/msix-packaging-ext/build-and-package.png) + +- **Display name** - Customize your task name +- **Output Path** - Specify the output path for the MSIX package that will be created by this task. The path in the example above uses [predefined variable](https://docs.microsoft.com/azure/devops/pipelines/build/variables) **Build.ArtifactStagingDirectory** which is the local path on the agent to store artifacts, and is used here to store task output files which can later be published using a publish artifacts task. +- **Build Solution with MSBuild** - Select this option to build your solution with msbuild for the specified target platform. Leave the box unchecked if you already have binaries that just need to be packaged. If you leave the box unchecked, you will be asked to provide the path to your binaries. +- **Project to Build** - Provide the path to your project or solution file that needs to be built. +- **Clean before Building** - Select this checkbox if you want the task to run a clean build prior to the build. +- **Generate MSIX Bundle** - Select this checkbox to generate an MSIX bundle instead of a package. Make sure to name your output file in the **Output Path** option with a .msixbundle extension instead of .msix. +- **Configuration** - Choose between *Debug* and *Release* build configurations. +- **Platform** - Specify the target build platform, for example, *x64*, *x86*, *Any CPU*. +- **Update App Version in Manifest** - Select this checkbox to change the app version from the one specified in the app's .appxmanifest file. This will not overwrite the .appxmanifest file, but change the app version in the generated output MSIX package. If this option is selected, you will be asked to provide the path to the manifest file, and the app version number to set for the app. +- **Application Package Distribution Mode** - Select the mode from the dropdown menu to generate a Store app package or non-Store app package. +- **Advanced Options for MSBuild** - Customize your MSBuild by using advanced options. + +### MSIX package signing + +![signing task](images/msix-packaging-ext/signing.png) + +- **Display name** - Customize your task name +- **Package to sign** - The MSIX package signing task uses SignTool to sign all files matching this path, regardless of whether they are MSIX packages or bundles. +- **Certificate file** - Select your trusted certificate to sign the app from the dropdown, or upload a certificate file by using the gear icon. +- **Password Variable** - The name of the variable which stores the password used to access the certificate file for signing. Note that this is NOT the password itself, but rather the variable name, which can be set under Library. +- **Time Stamp Server** - A URL that specifies the address of a time stamping server. This is an optional parameter. + +### App installer file for MSIX + +![appinstaller](images/msix-packaging-ext/app-installer.png) + +- **Display name** - Customize your task name +- **Package** - This is the path to the package or bundle you want to create an App Installer for. +- **Output File Path** - This is the path of the App Installer file to be written. +- **Method to Create App Installer File** - Choose whether to create a new App Installer file or update an existing one. If you choose to update an existing one, you will be asked to provide the path to the existing App Installer file. +- **Version for App Installer file** - The version number which will be given. Must take the form (*major*).(*minor*).(*build*).(*revision*). +- **URI** - Web URI to the redirected App Installer file. +- **Main Package/Bundle URI** - URI to the app package/bundle location. +- **Update On Launch** - Select this to set the app to check for updates when launched. If this checkbox is selected, you will be asked to provide details like *Hours Between Update Checks*, whether to *Show UI to User when Updating*, and whether you want the *Update to Block App Activation*. + +### Create package for MSIX app attach + +![app attach](images/msix-packaging-ext/app-attach.png) + +- **Display name** - Customize your task name. +- **Package Path** - This is the path to the MSIX package/bundle. +- **VHDX Output Path** - This is the path of the VHDX file that will be created by the task. +- **VHDX size** - The maximum size in MBs of the VHDX. + +After configuring all the tasks, you can use a *Publish build artifacts* task to drop all the artifacts from the temp location to Azure Pipelines artifacts or a file share of your choice. + +## Ways to provide Feedback + +We would love to hear your feedback on the *MSIX Packaging* Extension. Reach out to us via the following channels: + +- Review the extension on Azure DevOps Marketplace +- [MSIX Tech Community](https://techcommunity.microsoft.com/t5/msix/ct-p/MSIX) +- [GitHub open source project](https://github.com/microsoft/msix-packaging/tree/master/tools/pipelines-tasks) - The source code for this extension is a part of the MSIX SDK open source project, which welcomes contributions and suggestions. diff --git a/tools/pipelines-tasks/package-lock.json b/tools/pipelines-tasks/package-lock.json new file mode 100644 index 000000000..a8579bd42 --- /dev/null +++ b/tools/pipelines-tasks/package-lock.json @@ -0,0 +1,1267 @@ +{ + "name": "msix-ado-tasks-extension", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-OU2+C7X+5Gs42JZzXoto7yOQ0A0=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/form-data": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", + "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", + "dev": true + }, + "@types/mocha": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz", + "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", + "dev": true + }, + "@types/node": { + "version": "14.14.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.39.tgz", + "integrity": "sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw==", + "dev": true + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "dev": true + }, + "@types/qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==", + "dev": true + }, + "@types/shelljs": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.8.tgz", + "integrity": "sha512-lD3LWdg6j8r0VRBFahJVaxoW0SIcswxKaFUrmKl33RJVeeoNYQAz4uqCJ5Z6v4oIBOsC5GozX+I5SorIKiTcQA==", + "dev": true, + "requires": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "azure-devops-node-api": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-10.2.2.tgz", + "integrity": "sha512-4TVv2X7oNStT0vLaEfExmy3J4/CzfuXolEcQl/BRUmvGySqKStTG2O55/hUQ0kM7UJlZBLgniM0SBq4d/WkKow==", + "requires": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "azure-pipelines-task-lib": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-2.12.2.tgz", + "integrity": "sha512-ofAdVZcL90Qv6zYcKa1vK3Wnrl2kxoKX/Idvb7RWrqHQzcJlAEjCU4UCB5y6NnSKqRSyVTIhdS6hChphpOaiMQ==", + "requires": { + "minimatch": "3.0.4", + "mockery": "^1.7.0", + "q": "^1.1.2", + "semver": "^5.1.0", + "shelljs": "^0.3.0", + "sync-request": "3.0.1", + "uuid": "^3.0.1" + }, + "dependencies": { + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=" + }, + "sync-request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-3.0.1.tgz", + "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", + "requires": { + "concat-stream": "^1.4.7", + "http-response-object": "^1.0.1", + "then-request": "^2.0.1" + } + } + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "http-basic": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-2.5.1.tgz", + "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.6", + "http-response-object": "^1.0.0" + } + }, + "http-response-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-1.1.0.tgz", + "integrity": "sha1-p8TnWq6C87tJBOT0P2FWc7TVGMM=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "mime-db": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", + "dev": true + }, + "mime-types": { + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", + "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "dev": true, + "requires": { + "mime-db": "1.47.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.2.tgz", + "integrity": "sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + } + }, + "mockery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mockery/-/mockery-1.7.0.tgz", + "integrity": "sha1-9O3g2HUMHJcnwnLqLGBiniyaHE8=" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "picomatch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "sync-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", + "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", + "dev": true, + "requires": { + "http-response-object": "^3.0.1", + "sync-rpc": "^1.2.1", + "then-request": "^6.0.0" + }, + "dependencies": { + "@types/node": { + "version": "10.17.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.57.tgz", + "integrity": "sha512-9ejqfD/nkpl2RTUByUnkhE1xQFw6NWBE/CVsMuKnUvHRGm+HKFvSdHoyuJqKpG/N0hX7i3QHuf+OddN5WIHxMQ==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "http-basic": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", + "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "dev": true, + "requires": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + } + }, + "http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dev": true, + "requires": { + "@types/node": "^10.0.3" + } + }, + "promise": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", + "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", + "dev": true, + "requires": { + "asap": "~2.0.6" + } + }, + "then-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", + "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "dev": true, + "requires": { + "@types/concat-stream": "^1.6.0", + "@types/form-data": "0.0.33", + "@types/node": "^8.0.0", + "@types/qs": "^6.2.31", + "caseless": "~0.12.0", + "concat-stream": "^1.6.0", + "form-data": "^2.2.0", + "http-basic": "^8.1.1", + "http-response-object": "^3.0.1", + "promise": "^8.0.0", + "qs": "^6.4.0" + }, + "dependencies": { + "@types/node": { + "version": "8.10.66", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", + "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", + "dev": true + } + } + } + } + }, + "sync-rpc": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", + "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "dev": true, + "requires": { + "get-port": "^3.1.0" + } + }, + "then-request": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", + "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", + "requires": { + "caseless": "~0.11.0", + "concat-stream": "^1.4.7", + "http-basic": "^2.5.1", + "http-response-object": "^1.1.0", + "promise": "^7.1.1", + "qs": "^6.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "typed-rest-client": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", + "integrity": "sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg==", + "requires": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typescript": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "dev": true + }, + "underscore": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.0.tgz", + "integrity": "sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/tools/pipelines-tasks/package.json b/tools/pipelines-tasks/package.json new file mode 100644 index 000000000..37acd41bd --- /dev/null +++ b/tools/pipelines-tasks/package.json @@ -0,0 +1,31 @@ +{ + "name": "msix-ado-tasks-extension", + "version": "1.0.0", + "description": "", + "main": "createResjson.js", + "scripts": {}, + "repository": { + "type": "git", + "url": "git+https://github.com/microsoft/msix-ado-tasks-extension.git" + }, + "author": "Microsoft Corporation", + "license": "MIT", + "bugs": { + "url": "https://github.com/microsoft/msix-ado-tasks-extension/issues" + }, + "homepage": "https://github.com/microsoft/msix-ado-tasks-extension#readme", + "dependencies": { + "azure-devops-node-api": "^10.2.2", + "azure-pipelines-task-lib": "^2.12.2", + "shelljs": "^0.8.4" + }, + "devDependencies": { + "@types/mocha": "^7.0.2", + "@types/node": "^14.14.39", + "@types/q": "^1.5.4", + "@types/shelljs": "^0.8.8", + "mocha": "^8.3.2", + "sync-request": "^6.1.0", + "typescript": "^4.2.4" + } +} diff --git a/tools/pipelines-tasks/test/appattach.ts b/tools/pipelines-tasks/test/appattach.ts new file mode 100644 index 000000000..e71b6f8a8 --- /dev/null +++ b/tools/pipelines-tasks/test/appattach.ts @@ -0,0 +1,15 @@ +import assert = require('assert'); +import ttm = require('azure-pipelines-task-lib/mock-test'); + +import testHelpers = require('./testhelpers'); + +describe('MSIX app attach task tests', function () +{ + it('Should succeed with basic inputs', function (done: Mocha.Done) + { + const testRunner: ttm.MockTestRunner = testHelpers.runMockTest('appattach-success.js'); + testHelpers.assertTestRunnerSucceeded(testRunner); + assert.strictEqual(testHelpers.outputFileExists('TestVhdx.vhdx'), true, 'The VHDX should have been created'); + done(); + }); +}); \ No newline at end of file diff --git a/tools/pipelines-tasks/test/appinstallerfile.ts b/tools/pipelines-tasks/test/appinstallerfile.ts new file mode 100644 index 000000000..635a0154c --- /dev/null +++ b/tools/pipelines-tasks/test/appinstallerfile.ts @@ -0,0 +1,48 @@ +import assert = require('assert'); +import childProcess = require('child_process'); +import ttm = require('azure-pipelines-task-lib/mock-test'); + +import testHelpers = require('./testhelpers'); + +const runTestAndVerifyFileIsAsExpected = (testName: string) => +{ + const testRunner: ttm.MockTestRunner = testHelpers.runMockTest(testName + '.js'); + + testHelpers.assertTestRunnerSucceeded(testRunner); + testHelpers.assertOutputFileExists(testName + '.appinstaller'); + + const createdFilePath = testHelpers.outputFilePath(testName + '.appinstaller'); + const expectedFilePath = testHelpers.expectedFilePath(testName + '.appinstaller'); + + // Using Compare-Object instead of comparing the files ourselves + // prevents errors due to mismatched line endings. + const diff = childProcess.execSync(`powershell Compare-Object (Get-Content ${expectedFilePath}) (Get-Content ${createdFilePath})`).toString(); + assert.strictEqual(diff, '', `There should be no difference between the expected .appinstaller '${expectedFilePath}' and created .appinstaller '${createdFilePath}'.`); +} + +describe('App Installer file task tests', function () +{ + it('Should succeed creating a new App Installer file from a bundle', function (done: Mocha.Done) + { + runTestAndVerifyFileIsAsExpected('appinstallerfile-new-from-bundle-success'); + done(); + }); + + it('Should succeed updating an exsiting App Installer file from a bundle', function (done: Mocha.Done) + { + runTestAndVerifyFileIsAsExpected('appinstallerfile-update-from-bundle-success'); + done(); + }); + + it('Should succeed creating a new App Installer file from a package', function (done: Mocha.Done) + { + runTestAndVerifyFileIsAsExpected('appinstallerfile-new-from-package-success'); + done(); + }); + + it('Should succeed updating an existing App Installer file from a package', function (done: Mocha.Done) + { + runTestAndVerifyFileIsAsExpected('appinstallerfile-update-from-package-success'); + done(); + }); +}); \ No newline at end of file diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/App.xaml b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/App.xaml new file mode 100644 index 000000000..6b37ef389 --- /dev/null +++ b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/App.xaml @@ -0,0 +1,7 @@ + + + diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/App.xaml.cs b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/App.xaml.cs new file mode 100644 index 000000000..f9d7c3c83 --- /dev/null +++ b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/App.xaml.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +namespace HelloWorldApp +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + sealed partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + this.Suspending += OnSuspending; + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + Frame rootFrame = Window.Current.Content as Frame; + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (rootFrame == null) + { + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + + rootFrame.NavigationFailed += OnNavigationFailed; + + if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) + { + //TODO: Load state from previously suspended application + } + + // Place the frame in the current Window + Window.Current.Content = rootFrame; + } + + if (e.PrelaunchActivated == false) + { + if (rootFrame.Content == null) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + rootFrame.Navigate(typeof(MainPage), e.Arguments); + } + // Ensure the current window is active + Window.Current.Activate(); + } + } + + /// + /// Invoked when Navigation to a certain page fails + /// + /// The Frame which failed navigation + /// Details about the navigation failure + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + + /// + /// Invoked when application execution is being suspended. Application state is saved + /// without knowing whether the application will be terminated or resumed with the contents + /// of memory still intact. + /// + /// The source of the suspend request. + /// Details about the suspend request. + private void OnSuspending(object sender, SuspendingEventArgs e) + { + var deferral = e.SuspendingOperation.GetDeferral(); + //TODO: Save application state and stop any background activity + deferral.Complete(); + } + } +} diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/LockScreenLogo.scale-200.png b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 000000000..735f57adb Binary files /dev/null and b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/LockScreenLogo.scale-200.png differ diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/SplashScreen.scale-200.png b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/SplashScreen.scale-200.png new file mode 100644 index 000000000..023e7f1fe Binary files /dev/null and b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/SplashScreen.scale-200.png differ diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Square150x150Logo.scale-200.png b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 000000000..af49fec1a Binary files /dev/null and b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Square150x150Logo.scale-200.png differ diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Square44x44Logo.scale-200.png b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 000000000..ce342a2ec Binary files /dev/null and b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Square44x44Logo.scale-200.png differ diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 000000000..f6c02ce97 Binary files /dev/null and b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/StoreLogo.png b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/StoreLogo.png new file mode 100644 index 000000000..7385b56c0 Binary files /dev/null and b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/StoreLogo.png differ diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Wide310x150Logo.scale-200.png b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 000000000..288995b39 Binary files /dev/null and b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/Assets/Wide310x150Logo.scale-200.png differ diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/HelloWorldApp.csproj b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/HelloWorldApp.csproj new file mode 100644 index 000000000..a42e98d0f --- /dev/null +++ b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/HelloWorldApp.csproj @@ -0,0 +1,168 @@ + + + + + Debug + x86 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65} + AppContainerExe + Properties + HelloWorldApp + HelloWorldApp + en-US + UAP + 10.0.16299.0 + 10.0.10586.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true + false + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\ARM64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM64 + false + prompt + true + true + + + bin\ARM64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM64 + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + PackageReference + + + + App.xaml + + + MainPage.xaml + + + + + + Designer + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + + + 6.2.10 + + + + 14.0 + + + + \ No newline at end of file diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/HelloWorldApp.sln b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/HelloWorldApp.sln new file mode 100644 index 000000000..ffeb11ca2 --- /dev/null +++ b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/HelloWorldApp.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30204.135 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloWorldApp", "HelloWorldApp.csproj", "{9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|ARM.ActiveCfg = Debug|ARM + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|ARM.Build.0 = Debug|ARM + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|ARM.Deploy.0 = Debug|ARM + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|ARM64.Build.0 = Debug|ARM64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|x64.ActiveCfg = Debug|x64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|x64.Build.0 = Debug|x64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|x64.Deploy.0 = Debug|x64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|x86.ActiveCfg = Debug|x86 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|x86.Build.0 = Debug|x86 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Debug|x86.Deploy.0 = Debug|x86 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|ARM.ActiveCfg = Release|ARM + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|ARM.Build.0 = Release|ARM + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|ARM.Deploy.0 = Release|ARM + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|ARM64.ActiveCfg = Release|ARM64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|ARM64.Build.0 = Release|ARM64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|ARM64.Deploy.0 = Release|ARM64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|x64.ActiveCfg = Release|x64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|x64.Build.0 = Release|x64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|x64.Deploy.0 = Release|x64 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|x86.ActiveCfg = Release|x86 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|x86.Build.0 = Release|x86 + {9E4A4783-4F0B-44E6-8058-DFAD4C66DD65}.Release|x86.Deploy.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {758FE6B9-6E06-4E4B-95A9-03F495923140} + EndGlobalSection +EndGlobal diff --git a/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/MainPage.xaml b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/MainPage.xaml new file mode 100644 index 000000000..87ff734d7 --- /dev/null +++ b/tools/pipelines-tasks/test/assets/HelloWorldUWPApp/MainPage.xaml @@ -0,0 +1,15 @@ + + + +