PortableFlow.cpp (14818B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "PortableFlow.h" 5 #include "PortableInstaller.h" 6 #include "WorkflowBase.h" 7 #include <winget/Filesystem.h> 8 #include <winget/PortableFileEntry.h> 9 #include <winget/PortableIndex.h> 10 11 using namespace AppInstaller::Manifest; 12 using namespace AppInstaller::Repository; 13 using namespace AppInstaller::Utility; 14 using namespace AppInstaller::CLI::Portable; 15 using namespace AppInstaller::Portable; 16 using namespace AppInstaller::Repository::Microsoft; 17 18 namespace AppInstaller::CLI::Workflow 19 { 20 namespace 21 { 22 constexpr std::string_view s_DefaultSource = "*DefaultSource"sv; 23 24 std::string GetPortableProductCode(Execution::Context& context) 25 { 26 const std::string& packageId = context.Get<Execution::Data::Manifest>().Id; 27 28 std::string source; 29 if (context.Contains(Execution::Data::PackageVersion)) 30 { 31 source = context.Get<Execution::Data::PackageVersion>()->GetSource().GetIdentifier(); 32 } 33 else 34 { 35 source = s_DefaultSource; 36 } 37 38 return MakeSuitablePathPart(packageId + "_" + source); 39 } 40 41 void EnsureValidArgsForPortableInstall(Execution::Context& context) 42 { 43 std::string_view renameArg = context.Args.GetArg(Execution::Args::Type::Rename); 44 45 try 46 { 47 if (MakeSuitablePathPart(renameArg) != renameArg) 48 { 49 context.Reporter.Error() << Resource::String::ReservedFilenameError << std::endl; 50 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS); 51 } 52 } 53 catch (...) 54 { 55 context.Reporter.Error() << Resource::String::ReservedFilenameError << std::endl; 56 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS); 57 } 58 } 59 60 void EnsureVolumeSupportsReparsePoints(Execution::Context& context) 61 { 62 Manifest::ScopeEnum scope = ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); 63 const std::filesystem::path& symlinkDirectory = GetPortableLinksLocation(scope); 64 65 if (!AppInstaller::Filesystem::SupportsReparsePoints(symlinkDirectory)) 66 { 67 context.Reporter.Error() << Resource::String::ReparsePointsNotSupportedError << std::endl; 68 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED); 69 } 70 } 71 } 72 73 void VerifyPackageAndSourceMatch(Execution::Context& context) 74 { 75 const std::string& packageIdentifier = context.Get<Execution::Data::Manifest>().Id; 76 77 std::string sourceIdentifier; 78 if (context.Contains(Execution::Data::PackageVersion)) 79 { 80 sourceIdentifier = context.Get<Execution::Data::PackageVersion>()->GetSource().GetIdentifier(); 81 } 82 else 83 { 84 sourceIdentifier = s_DefaultSource; 85 } 86 87 PortableInstaller& portableInstaller = context.Get<Execution::Data::PortableInstaller>(); 88 if (portableInstaller.ARPEntryExists()) 89 { 90 if (packageIdentifier != portableInstaller.WinGetPackageIdentifier || sourceIdentifier != portableInstaller.WinGetSourceIdentifier) 91 { 92 if (!context.Args.Contains(Execution::Args::Type::Force)) 93 { 94 AICLI_LOG(CLI, Error, << "Registry match failed, skipping write to uninstall registry"); 95 context.Reporter.Error() << Resource::String::PortablePackageAlreadyExists << std::endl; 96 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS); 97 } 98 else 99 { 100 AICLI_LOG(CLI, Info, << "Overriding registry match check..."); 101 context.Reporter.Warn() << Resource::String::PortableRegistryCollisionOverridden << std::endl; 102 } 103 } 104 } 105 106 portableInstaller.WinGetPackageIdentifier = packageIdentifier; 107 portableInstaller.WinGetSourceIdentifier = sourceIdentifier; 108 } 109 110 void InitializePortableInstaller(Execution::Context& context) 111 { 112 Manifest::ScopeEnum scope = Manifest::ScopeEnum::Unknown; 113 bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); 114 std::shared_ptr<Repository::IPackageVersion> installedVersion; 115 if (context.Contains(Execution::Data::InstalledPackageVersion)) 116 { 117 installedVersion = context.Get<Execution::Data::InstalledPackageVersion>(); 118 } 119 if (isUpdate && installedVersion) 120 { 121 IPackageVersion::Metadata installationMetadata = installedVersion->GetMetadata(); 122 auto installerScopeItr = installationMetadata.find(Repository::PackageVersionMetadata::InstalledScope); 123 if (installerScopeItr != installationMetadata.end()) 124 { 125 scope = Manifest::ConvertToScopeEnum(installerScopeItr->second); 126 } 127 } 128 else 129 { 130 if (context.Args.Contains(Execution::Args::Type::InstallScope)) 131 { 132 scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); 133 } 134 else 135 { 136 Manifest::ScopeEnum requiredScope = Settings::User().Get<Settings::Setting::InstallScopeRequirement>(); 137 Manifest::ScopeEnum preferredScope = Settings::User().Get<Settings::Setting::InstallScopePreference>(); 138 139 scope = requiredScope != Manifest::ScopeEnum::Unknown ? requiredScope : preferredScope; 140 } 141 } 142 143 const auto& installer = context.Get<Execution::Data::Installer>().value(); 144 Utility::Architecture arch = installer.Arch; 145 const std::string& productCode = GetPortableProductCode(context); 146 147 PortableInstaller portableInstaller = PortableInstaller(scope, arch, productCode); 148 portableInstaller.IsUpdate = isUpdate; 149 150 if (IsArchiveType(installer.BaseInstallerType) && installer.ArchiveBinariesDependOnPath) 151 { 152 portableInstaller.BinariesDependOnPath = true; 153 } 154 155 // Set target install directory 156 std::string_view locationArg = context.Args.GetArg(Execution::Args::Type::InstallLocation); 157 std::filesystem::path targetInstallDirectory; 158 159 if (!locationArg.empty()) 160 { 161 targetInstallDirectory = std::filesystem::path{ ConvertToUTF16(locationArg) }; 162 } 163 else 164 { 165 targetInstallDirectory = GetPortableInstallRoot(scope, arch); 166 targetInstallDirectory /= ConvertToUTF16(productCode); 167 } 168 169 portableInstaller.TargetInstallLocation = targetInstallDirectory; 170 portableInstaller.SetAppsAndFeaturesMetadata(context.Get<Execution::Data::Manifest>(), installer.AppsAndFeaturesEntries); 171 context.Add<Execution::Data::PortableInstaller>(std::move(portableInstaller)); 172 } 173 174 std::vector<PortableFileEntry> GetDesiredStateForPortableInstall(Execution::Context& context) 175 { 176 std::filesystem::path& installerPath = context.Get<Execution::Data::InstallerPath>(); 177 PortableInstaller& portableInstaller = context.Get<Execution::Data::PortableInstaller>(); 178 std::vector<PortableFileEntry> entries; 179 180 const std::filesystem::path& targetInstallDirectory = portableInstaller.TargetInstallLocation; 181 const std::filesystem::path& symlinkDirectory = GetPortableLinksLocation(portableInstaller.GetScope()); 182 183 // InstallerPath will point to a directory if it is extracted from an archive. 184 if (std::filesystem::is_directory(installerPath)) 185 { 186 portableInstaller.RecordToIndex = true; 187 188 for (const auto& entry : std::filesystem::directory_iterator(installerPath)) 189 { 190 std::filesystem::path entryPath = entry.path(); 191 PortableFileEntry portableFile; 192 std::filesystem::path relativePath = std::filesystem::relative(entryPath, entryPath.parent_path()); 193 std::filesystem::path targetPath = targetInstallDirectory / relativePath; 194 195 if (std::filesystem::is_directory(entryPath)) 196 { 197 entries.emplace_back(std::move(PortableFileEntry::CreateDirectoryEntry(entryPath, targetPath))); 198 } 199 else 200 { 201 entries.emplace_back(std::move(PortableFileEntry::CreateFileEntry(entryPath, targetPath, {}))); 202 } 203 } 204 205 const std::vector<Manifest::NestedInstallerFile>& nestedInstallerFiles = context.Get<Execution::Data::Installer>()->NestedInstallerFiles; 206 207 for (const auto& nestedInstallerFile : nestedInstallerFiles) 208 { 209 const std::filesystem::path& targetPath = targetInstallDirectory / ConvertToUTF16(nestedInstallerFile.RelativeFilePath); 210 211 std::filesystem::path commandAlias; 212 if (nestedInstallerFile.PortableCommandAlias.empty()) 213 { 214 commandAlias = targetPath.filename(); 215 } 216 else 217 { 218 commandAlias = ConvertToUTF16(nestedInstallerFile.PortableCommandAlias); 219 } 220 221 Filesystem::AppendExtension(commandAlias, ".exe"); 222 entries.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkDirectory / commandAlias, targetPath))); 223 } 224 } 225 else 226 { 227 std::string_view renameArg = context.Args.GetArg(Execution::Args::Type::Rename); 228 const std::vector<string_t>& commands = context.Get<Execution::Data::Installer>()->Commands; 229 std::filesystem::path commandAlias = installerPath.filename(); 230 231 if (!commands.empty()) 232 { 233 commandAlias = ConvertToUTF16(commands[0]); 234 } 235 236 if (!renameArg.empty()) 237 { 238 commandAlias = ConvertToUTF16(renameArg); 239 } 240 AppInstaller::Filesystem::AppendExtension(commandAlias, ".exe"); 241 242 const std::filesystem::path& targetFullPath = targetInstallDirectory / commandAlias; 243 entries.emplace_back(std::move(PortableFileEntry::CreateFileEntry(installerPath, targetFullPath, {}))); 244 entries.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkDirectory / commandAlias, targetFullPath))); 245 } 246 247 return entries; 248 } 249 250 void PortableInstallImpl(Execution::Context& context) 251 { 252 OperationType installType = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate) ? OperationType::Upgrade : OperationType::Install; 253 254 PortableInstaller& portableInstaller = context.Get<Execution::Data::PortableInstaller>(); 255 try 256 { 257 context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; 258 259 std::vector<AppInstaller::Portable::PortableFileEntry> desiredState = GetDesiredStateForPortableInstall(context); 260 261 portableInstaller.SetDesiredState(desiredState); 262 263 if (!portableInstaller.VerifyExpectedState()) 264 { 265 if (context.Args.Contains(Execution::Args::Type::Force)) 266 { 267 context.Reporter.Warn() << Resource::String::PortableHashMismatchOverridden << std::endl; 268 } 269 else 270 { 271 context.Reporter.Warn() << Resource::String::PortableHashMismatchOverrideRequired << std::endl; 272 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED); 273 } 274 } 275 276 portableInstaller.Install(installType); 277 context.Add<Execution::Data::CorrelatedAppsAndFeaturesEntries>({ portableInstaller.GetAppsAndFeaturesEntry() }); 278 context.Add<Execution::Data::OperationReturnCode>(ERROR_SUCCESS); 279 context.Reporter.Warn() << portableInstaller.GetOutputMessage(); 280 } 281 catch (...) 282 { 283 context.Add<Execution::Data::OperationReturnCode>(Workflow::HandleException(context, std::current_exception())); 284 285 if (!portableInstaller.IsUpdate) 286 { 287 context.Reporter.Warn() << Resource::String::PortableInstallFailed << std::endl; 288 portableInstaller.PrepareForCleanUp(); 289 ; portableInstaller.Uninstall(); 290 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED); 291 } 292 } 293 } 294 295 void PortableUninstallImpl(Execution::Context& context) 296 { 297 PortableInstaller& portableInstaller = context.Get<Execution::Data::PortableInstaller>(); 298 299 try 300 { 301 context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl; 302 303 if (!portableInstaller.VerifyExpectedState()) 304 { 305 if (context.Args.Contains(Execution::Args::Type::Force)) 306 { 307 context.Reporter.Warn() << Resource::String::PortableHashMismatchOverridden << std::endl; 308 } 309 else 310 { 311 context.Reporter.Warn() << Resource::String::PortableHashMismatchOverrideRequired << std::endl; 312 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED); 313 } 314 } 315 316 portableInstaller.Purge = context.Args.Contains(Execution::Args::Type::Purge) || 317 (!portableInstaller.IsUpdate && Settings::User().Get<Settings::Setting::UninstallPurgePortablePackage>() && !context.Args.Contains(Execution::Args::Type::Preserve)); 318 319 portableInstaller.Uninstall(); 320 context.Add<Execution::Data::OperationReturnCode>(ERROR_SUCCESS); 321 context.Reporter.Warn() << portableInstaller.GetOutputMessage(); 322 } 323 catch (...) 324 { 325 context.Add<Execution::Data::OperationReturnCode>(Workflow::HandleException(context, std::current_exception())); 326 } 327 } 328 329 void EnsureSupportForPortableInstall(Execution::Context& context) 330 { 331 auto installerType = context.Get<Execution::Data::Installer>().value().EffectiveInstallerType(); 332 333 if (installerType == InstallerTypeEnum::Portable) 334 { 335 context << 336 EnsureValidArgsForPortableInstall << 337 EnsureVolumeSupportsReparsePoints; 338 } 339 } 340 }