Problem
ContentManifest.InstallationInstructions (PreInstallSteps / PostInstallSteps) exists as a per-manifest, publisher-agnostic hook for installation-time actions, but nothing in the codebase executes it. ContentManifestBuilder.AddPreInstallStep / AddPostInstallStep are the only writers; there is no reader anywhere in the pipeline.
It is also not reliably carried through delivery today:
CommunityOutpostDeliverer, GitHubManifestFactory, SuperHackersManifestFactory, and CommunityOutpostManifestFactory forward the full InstallationInstructions object.
HttpContentDeliverer and FileSystemDeliverer only forward WorkspaceStrategy via WithInstallationInstructions(workspaceStrategy), silently dropping any Pre/PostInstallSteps set upstream.
Raised in #336: GeneralsOnline's 060526_QFE1 portable needs a one-time step after extraction (run EasyAntiCheat_EOS_Setup.exe if not already installed) before the content is usable. That need is generic — any publisher may eventually need to run an installer, remove a stale file, or rename an extracted artifact once, at acquisition time — not GeneralsOnline-specific.
Why a generic executor needs care, not just wiring
InstallationStep today is Name + free-form Command + free-form Arguments + WorkingDirectory + RequiresElevation. Executing that as-is means any deliverer — including GitHub-sourced releases and other third-party providers — can declare an elevated, arbitrary command in manifest data. That is a real trust-boundary gap, not a hypothetical one, and it should not be closed by simply adding a generic foreach (var step in instructions.PostInstallSteps) Process.Start(...).
Proposed approach
- Replace (or extend)
InstallationStep's free-form Command/Arguments with a closed, typed Kind (e.g. RunVerifiedInstaller, RemoveFile, RenameFile), each with its own structured parameters, so the executor is auditable rather than "run anything."
- Restrict any file a step references (installer, target file) to a path that resolves inside the manifest's own
Files list (content-addressable, hash-verified) and inside the delivered/workspace directory — the same containment check GameLauncher already applies to the main executable path.
- Fix
HttpContentDeliverer / FileSystemDeliverer to forward Pre/PostInstallSteps, not just WorkspaceStrategy.
- Add an
InstallationInstructionsService (or similar) invoked once per content acquisition, after a deliverer finishes producing files, that enumerates and executes a manifest's PostInstallSteps.
- Decide, and gate behind an allowlist if needed, which publishers are trusted to declare executable steps at all until this has had a security pass — see the Security & Integrity Validation milestone.
Acceptance criteria
- A manifest's
PostInstallSteps survive every delivery path unchanged, including HttpContentDeliverer and FileSystemDeliverer.
- A generic service executes typed, closed-kind steps only; there is no path from manifest data to an arbitrary shell command.
GeneralsOnlineManifestFactory can declare "run EasyAntiCheat_EOS_Setup.exe if not installed" as a PostInstallStep and have it actually execute once, at acquisition time.
Explicitly out of scope
Related
Problem
ContentManifest.InstallationInstructions(PreInstallSteps/PostInstallSteps) exists as a per-manifest, publisher-agnostic hook for installation-time actions, but nothing in the codebase executes it.ContentManifestBuilder.AddPreInstallStep/AddPostInstallStepare the only writers; there is no reader anywhere in the pipeline.It is also not reliably carried through delivery today:
CommunityOutpostDeliverer,GitHubManifestFactory,SuperHackersManifestFactory, andCommunityOutpostManifestFactoryforward the fullInstallationInstructionsobject.HttpContentDelivererandFileSystemDelivereronly forwardWorkspaceStrategyviaWithInstallationInstructions(workspaceStrategy), silently dropping anyPre/PostInstallStepsset upstream.Raised in #336: GeneralsOnline's
060526_QFE1portable needs a one-time step after extraction (runEasyAntiCheat_EOS_Setup.exeif not already installed) before the content is usable. That need is generic — any publisher may eventually need to run an installer, remove a stale file, or rename an extracted artifact once, at acquisition time — not GeneralsOnline-specific.Why a generic executor needs care, not just wiring
InstallationSteptoday isName+ free-formCommand+ free-formArguments+WorkingDirectory+RequiresElevation. Executing that as-is means any deliverer — including GitHub-sourced releases and other third-party providers — can declare an elevated, arbitrary command in manifest data. That is a real trust-boundary gap, not a hypothetical one, and it should not be closed by simply adding a genericforeach (var step in instructions.PostInstallSteps) Process.Start(...).Proposed approach
InstallationStep's free-formCommand/Argumentswith a closed, typedKind(e.g.RunVerifiedInstaller,RemoveFile,RenameFile), each with its own structured parameters, so the executor is auditable rather than "run anything."Fileslist (content-addressable, hash-verified) and inside the delivered/workspace directory — the same containment checkGameLauncheralready applies to the main executable path.HttpContentDeliverer/FileSystemDelivererto forwardPre/PostInstallSteps, not justWorkspaceStrategy.InstallationInstructionsService(or similar) invoked once per content acquisition, after a deliverer finishes producing files, that enumerates and executes a manifest'sPostInstallSteps.Acceptance criteria
PostInstallStepssurvive every delivery path unchanged, includingHttpContentDelivererandFileSystemDeliverer.GeneralsOnlineManifestFactorycan declare "runEasyAntiCheat_EOS_Setup.exeif not installed" as aPostInstallStepand have it actually execute once, at acquisition time.Explicitly out of scope
IGameLaunchPrerequisitein GeneralsOnline Easy Anti-Cheat update breaks launch: wrong executable, missing EAC install step #336, not here.Related
IGameLaunchPrerequisite/GeneralsOnlineEacLaunchPrerequisite) is tracked.