Skip to content

issue 86 macos pkg install scripts

Kazushi Kamegawa edited this page Aug 17, 2026 · 1 revision

Issue #86: macos — pre/post-install script support (with sub-issues #87 / #88 / #89 / #90)

日本語

Tracking

Context

The operator distributes PowerShell 7 for macOS through a real Intune tenant using a pre-install script (removes a Homebrew-installed pwsh, removes a stale pwsh symlink, checks free disk space) and a post-install script (verifies the install, adds /usr/local/bin to /etc/paths.d). Relaypublisher's manifest currently has no way to express this.

The Graph API already supports it (verified via Microsoft Learn):

doc/01-manifest-schema.md §5.4 already documents this constraint in its comparison table ("pre/post install script: lob=no / pkg=yes"), but no schema field, validation, or Graph mapping exists yet.

Design

Manifest schema (under an app entry, macOS AppType: pkg only)

  - Platform: macos
    Architecture: arm64
    InstallerType: pkg
    AppType: pkg
    DisplayName: PowerShell [macOS Arm64]

    Scripts:                                          # optional, AppType: pkg only
      PreInstall: scripts/macos/powershell/preinstall.sh
      PostInstall: scripts/macos/powershell/postinstall.sh
  • Values are repository-relative paths (resolved from --repo-root), the same convention as Icon and Detection.ScriptFile.
  • Either PreInstall or PostInstall alone is allowed; the Scripts block itself is optional.

Key decisions

  1. AppType: lob or Platform: windows with a Scripts block is a validation error — Graph has no such property there.
  2. Script content is not included in the deterministic inputHash — same precedent as Icon / Detection.ScriptFile. The app metadata PATCH (UpdateAppAsync) always runs on publish, so script edits are picked up without forcing a full re-upload of a package that can be up to 8 GB.
  3. PlanService.EnumerateReferencedFiles is extended to include the script paths, so scripts/** changes are picked up by changed detection.
  4. Line endings are normalized CRLF → LF immediately before base64 encoding (a Windows-checked-out .sh with CRLF breaks the shebang on macOS).
  5. A UTF-8 BOM is a validation error (a BOM before the shebang prevents the script from launching).
  6. A file not starting with a shebang (#!) is a validation error, per Intune's shell-script prerequisites.

Validation layering (fail before any Graph call)

ManifestValidator (pure, no I/O):

  • Scripts set on Platform: windows → error
  • Scripts set on AppType: lob → error
  • PreInstall / PostInstall failing path-safety checks (traversal, absolute path) → error
  • Extension other than .sh → error
  • Scripts block present but both fields null → error

ManifestAssetValidator (needs --repo-root, same location as the Icon checks):

  • File does not exist → error
  • 15360 characters or more → error
  • UTF-8 BOM present → error
  • Does not start with a shebang → error

Files touched

  • Core model/validation: MacOsScriptsManifest.cs (new), AppManifest.cs, ManifestValues.cs, ManifestValidator.cs, ManifestAssetValidator.cs
  • Publish path: MacOsAppPayload.cs, MacOsAppPayloadMapper.cs, ManifestAssetReader.cs, MacOsAppPublisher.cs, Planning/PlanService.cs
  • Docs: doc/00-overview.md §6.13, doc/01-manifest-schema.md §5.3/§5.4 (+ new §5.4.2), README.md/_ja, doc/05-operation.md/_ja, doc/06-troubleshooting.md/_ja, doc/issues/issue-020-macos-pkg-install-scripts.md (new), doc/relaypublisher-design-and-copilot-issues.md
  • Samples: samples/scripts/macos/powershell/{preinstall,postinstall}.sh (new), PowerShell 7.6.5 macOS manifests, samples/manifests/README.md/_ja
  • Tests: MSTest additions across ManifestValidationTests, ManifestAssetValidatorTests, MacOsAppPayloadMapperTests, GraphMacOsAppClientTests, ManifestLoaderTests, PlanService reverse-lookup tests

Out of scope

  • Script support for macOSLobApp / macOSDmgApp (the Graph property does not exist there)
  • Managing standalone shell script policies (deviceShellScript)
  • Script linting (shellcheck etc.) — left to the consuming repository's own CI

Verification

dotnet build IntuneLobPublisher.slnx
dotnet test IntuneLobPublisher.slnx
dotnet run --project src/IntuneLobPublisher.Cli -- validate --repo-root samples --manifest-root manifests
dotnet run --project src/IntuneLobPublisher.Cli -- package --repo-root samples --manifest samples/manifests/Microsoft/Microsoft.PowerShell/7.6.5/powershell-macos-arm64.yaml --output out
dotnet run --project src/IntuneLobPublisher.Cli -- publish --repo-root samples --manifest samples/manifests/Microsoft/Microsoft.PowerShell/7.6.5/powershell-macos-arm64.yaml --package-dir out --dry-run

Negative-path checks: a missing Scripts.PreInstall path, a Scripts block on an AppType: lob entry, and a .sh file over 15360 characters must each fail validate before any Graph call.

Real-tenant verification (optional, operator environment): after publish, confirm the Intune admin center shows the pre/post-install scripts on the app's "Program" tab, and that GET /beta/deviceAppManagement/mobileApps/{id} returns matching base64-decoded preInstallScript.scriptContent / postInstallScript.scriptContent.

Clone this wiki locally