Documentation

How Wrappify works

Wrappify turns any EXE, MSI or MSP installer into a deploy-ready Microsoft Intune Win32 app. This guide explains every function in detail — from inspecting an installer to deploying it through Microsoft Graph, with supersedence, assignment groups and a shared App Catalog along the way.

Getting started

Overview

Packaging an application for Microsoft Intune normally means hand-crafting silent install commands, writing detection rules, running IntuneWinAppUtil.exe, and clicking through the Intune portal to upload it. Wrappify automates the whole lifecycle and keeps a human in the loop for review.

You upload an installer; Wrappify:

  1. extracts deterministic metadata (publisher, product, version, MSI ProductCode, PE VersionInfo, architecture, installer-type heuristics);
  2. asks an LLM to propose a complete install profile from that metadata;
  3. lets you review and edit every field in the browser;
  4. builds the .intunewin package; and
  5. deploys it to your tenant's Intune via Microsoft Graph — optionally with supersedence relationships and Entra ID assignment groups.
Free to use Wrappify is free — you bring your own LLM API key (Anthropic, OpenAI, xAI Grok or Google Gemini) and pay your provider directly. Nothing is billed by Wrappify.

Core concepts

TermWhat it means in Wrappify
Installer profileThe Pydantic-validated JSON describing how to install, detect, and present the app: install/uninstall commands, install context, detection rule, display metadata, requirements, icon. It's the single object you edit on the Review page.
Detection ruleHow Intune decides whether the app is already installed. Exactly one per app: MSI Product Code, Registry, File, or PowerShell script.
.intunewinMicrosoft's encrypted package format for Win32 apps, produced by IntuneWinAppUtil.exe (or the cross-platform ContentPrep module).
Win32LobAppThe Microsoft Graph resource type Wrappify creates when it deploys your app to Intune.
SupersedenceAn Intune relationship that marks a new app version as replacing an older one, so devices update cleanly.
TenantA customer Microsoft Entra ID organization. All data is strictly isolated per tenant.
Companion fileAny extra file that must ship inside the package alongside the main installer.

Quick start

  1. Sign in. Open the app and sign in with your Microsoft Entra ID account. If your tenant hasn't onboarded yet, you'll see a one-time admin-consent link — see Multi-tenancy.
  2. Set your LLM key. Go to Settings and add your API key for Anthropic (recommended), OpenAI, Grok or Gemini. See Settings.
  3. Upload an installer. Drop an .exe, .msi, .msp, or a .zip of a whole installer folder. You can also paste a URL.
  4. Review the proposal. Wrappify inspects the file and the LLM fills in the profile. Edit anything that's off.
  5. Build & deploy. Build the .intunewin, then deploy it straight to Intune — optionally with supersedence and assignment groups.

The pipeline

Every upload flows through the same stages, whether it came from a file, a ZIP or a URL:

UploadEXE / MSI / MSP / ZIP / URL
Inspectmetadata + heuristics
GenerateLLM profile
Reviewedit in the UI
Build.intunewin
DeployGraph → Intune
Inputs

Uploading installers

The upload page accepts a single .exe, .msi or .msp installer. Uploads are validated by file extension, magic-byte check and a maximum size (configured by the operator, default 1 GB). The installer's bytes are stored under your tenant's namespace and never shared with another tenant.

Source: file or URL

A File / URL toggle lets you either upload from your browser or have the backend fetch the installer from a URL — handy for large vendor downloads. See Add from URL for the SSRF safeguards.

Catalog short-circuit If a published App Catalog entry already has the exact same installer (matched by SHA-256), Wrappify skips the LLM call entirely and pre-fills the profile from the catalog entry.

ZIP folder upload & setup-file detection

Many installers ship as a folder of files (a setup EXE plus DLLs, MSTs, config). Upload a .zip of the whole folder and Wrappify extracts it safely — with guards against zip-slip, zip-bombs and excessive file counts; macOS cruft (__MACOSX, .DS_Store) is skipped.

Picking the setup file

Wrappify ranks the extracted files to choose the primary setup file:

  • Deterministic heuristic: MSI > known EXE installer > unknown EXE; a bonus for setup/install names; a penalty for unins, vc_redist, dotnet; file size as a tiebreak.
  • If the top two candidates are close, a single LLM tiebreak decides.

Every other extracted file is automatically staged as a companion (source zip), so your whole folder ends up inside the package. You can override the choice on the Review page via the Setup file selector — promoting a companion to the setup file regenerates the profile.

Multi-architecture: one app per architecture

When you have separate installers per CPU architecture (x64, ARM64, x86), choose One app per architecture for a ZIP. Wrappify creates one Intune app per installer, each with its detected requirements.architecture, so on deploy each app advertises the matching Graph allowedArchitectures and devices self-select the right build.

How architecture is detected

  • MSI / MSP — from the Summary-Info Template field (msiinfo suminfo).
  • EXE — primarily from filename tokens (the stub's own PE machine is often x86 even for a 64-bit payload), with the PE FILE_HEADER.Machine as a weak fallback.

Each app then flows the standard review → build → deploy path independently. The Review page's Requirements card shows the detected architecture so you can correct it before deploying.

ARM64 ARM64 is only valid in Graph's allowedArchitectures, never in the legacy applicableArchitectures field — Wrappify handles this distinction for you.

Add a file via URL

Instead of uploading from the browser, you can give Wrappify a URL and the backend downloads the installer (or a companion). This works for all three ingest modes: single file, ZIP, and multi-architecture ZIP.

SSRF safeguards

Server-side fetching is guarded against server-side request forgery:

  • http/https only;
  • redirects are followed manually with per-hop DNS resolution that rejects private, loopback, link-local, reserved and multicast IPs;
  • an optional host allowlist (URL_UPLOAD_ALLOWED_HOSTS);
  • the same size cap as browser uploads, plus filename/suffix validation (from Content-Disposition or the URL path).

Companion files

A package often needs more than the installer: transform files (MST), config, license files, or a secondary payload. The Companion files panel on the Review page lets you add these — by upload or by URL — and they're staged into the build source folder alongside the setup file.

New Teams offline MSIX

For the New Teams bootstrapper (teamsbootstrapper.exe), Wrappify can auto-fetch the offline Teams MSIX from Microsoft's CDN (or you upload it manually). Adding it switches the install command to -p -o "teams.msix" for a fully offline provisioning install.

Virus scanning

Operators can enable virus scanning of every upload before it is persisted, inspected or built. It's off by default and wired into every ingest path — single file, ZIP, multi-architecture and add-from-URL, plus companion files.

  • ClamAV (default) streams the bytes to a clamd daemon for an INSTREAM scan.
  • VirusTotal does a hash lookup — only the SHA-256 leaves the host, never the file itself.

A confirmed detection hard-blocks the upload with HTTP 422 — the file is never stored. A scanner outage is fail-open by default and can be switched to fail-closed. Like inspection, the scanner reads bytes only; it never executes the installer.

Generating the app

Inspection

Inspection reads metadata from the installer without ever executing it. What's read depends on the installer type.

MSI packages

For MSI, almost everything is read deterministically from the property table:

FieldSource / value
IdentityProductCode, ProductName, ProductVersion, Manufacturer, UpgradeCode
Silent installmsiexec /i "<file>.msi" /qn /norestart
Silent uninstallmsiexec /x <ProductCode> /qn /norestart
DetectionMSI Product Code (natively supported by Intune — preferred)
Logging/L*v "%TEMP%\<ProductName>.log"

EXE installers & heuristics

For EXE installers the inspector reads PE VersionInfo via pefile and determines the installer framework from PE signatures, resource strings and magic bytes. The framework fixes the silent-install switches:

Installer typeTypical silent switches
InnoSetup/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-
NSIS/S
InstallShield/s /v"/qn" (may need setup.iss)
WiX Burn bundle/quiet /norestart
Squirrel (Electron)--silent (installs in user context)
MSI-wrapped EXE/exenoui /qn
New Teams bootstrapper-p / -x, with AppX PowerShell detection

AI profile generation

The deterministic metadata is handed to an LLM, which proposes the complete install profile: silent install/uninstall commands, install context (system vs. user), detection rule, display name, publisher, description, category, suggested icon URL, and requirements (OS architecture, minimum OS, disk space).

How it's kept reliable

  • Strict JSON. The model must return JSON matching the InstallerProfile schema, embedded in the prompt. Output is validated with Pydantic; on a schema error there are up to two retries with the error fed back.
  • Single source of truth. The system prompt lives in prompts/installer_profile.md, never inline in code.
  • Field-learned lessons. Hard-won rules ("when X, do Y, because Z") accumulate in prompts/lessons.md and are injected into the prompt.
  • Caching. Responses are cached by the installer's SHA-256, scoped per tenant — the same file never costs a second API call.

The feedback loop

When you edit and save a profile on the Review page, your corrected version is upserted into the per-tenant cache for that file hash. A future re-upload of the same file returns your corrected profile, not the LLM's original.

Choosing a provider

The default and recommended provider is Anthropic Claude — the prompt and lessons are tuned for it. Tenants can switch to OpenAI, xAI Grok or Google Gemini in Settings, each with their own key. Token usage is logged per request.

Review & edit

The Review page is where you confirm or correct everything before building. Nothing is sent to Intune until you say so. It surfaces:

  • Install commands & context — install/uninstall command lines and system-vs-user context.
  • Detection rule editor — pick the rule type and edit its fields (see Detection rules).
  • Display metadata — name, publisher, description, category, icon.
  • Requirements card — architecture and minimum OS, so detected values are visible and correctable.
  • Setup-file selector — for ZIP uploads, shows the chosen setup file with an override dropdown.
  • Companion files panel — add/remove files that ship in the package.
  • Supported-parameters panel — read-only reference of the installer's documented switches (see below).
  • Supersedence & dependency panels — wire up version relationships before deploy.

Your edits are saved back to the profile and into the per-tenant cache.

Detection rules

Intune uses exactly one detection rule per app to decide if it's already installed. Wrappify chooses in strict priority order:

  1. MSI Product Code — MSI only; natively supported by Intune.
  2. Registry key — the HKLM\…\Uninstall\ key (or WOW6432Node) with DisplayName + a DisplayVersion ≥ X comparison.
  3. File presence — with an optional version check.
  4. PowerShell detection script — only when 1–3 don't fit. Supported by the model and editable in the UI.
Editing the rule The detection-rule editor lets you switch rule type and write a PowerShell script by hand when the heuristics or model didn't pick the right approach (e.g. AppX/Teams).

Supported-parameters reference

The Review page shows a collapsible Supported parameters panel — the documented command-line switches an installer framework accepts (what --help//? would print). These are derived from the detected installer type, without executing the installer, because the switch set is fixed by the framework, not the app.

For reference only — never sent to Intune The supported-parameters data is informational. It is structurally excluded from the Win32LobApp payload (there's a regression test asserting no switch/help string leaks).

Install wrapper for config & registry files

Some apps need more than a silent install — they need configuration applied afterwards. When you attach config or registry companions (.reg, .xml, .json, .cfg, .config, .ini), Wrappify can generate a PowerShell install_wrapper.ps1 that runs the original installer and then applies those files at the right location on the endpoint. The install command is rewritten to invoke the script.

Admin-reviewed before staging

  • Preview first. Generation returns a proposed script with a rationale and cited sources — nothing is persisted yet.
  • Edit, then confirm. You edit the script and confirm; only then is it staged as a companion and the command rewritten. The original command is stashed so a one-click revert restores it exactly.
  • Uninstall symmetry. The wrapper can carry a matching uninstall script (e.g. reg delete for .reg files), rewriting the uninstall command too.
Web search (Anthropic only) The generator can optionally use web search to ground the script in vendor docs. This is effective on Anthropic Claude; other providers fall back to model knowledge with a note in the UI.

Localization composer

The localization composer adds UI languages to a package. Describe what you need in free text and/or pick locales on the Review page; Wrappify produces a reviewable localization plan and, on confirm, fetches the language artifacts, bundles them as companions and rewrites the install command.

"Add a language" isn't one mechanism

Every plan carries a strategy, because different apps localize differently:

  • os_driven — the app follows the OS language (Chrome, Edge, VLC, 7-Zip); applying is an honest no-op.
  • bundled_langpack — language packs are fetched and bundled (e.g. Firefox / Thunderbird .xpi langpacks plus a policy wrapper).
  • msi_transform, separate_installer, config_driven, unknown — handled per case.

Recipe-first, AI-fallback

Wrappify tries a deterministic recipe first (no LLM call, version-matched artifact URLs); when no recipe fits it falls back to the LLM, optionally with web search. The plan's source — recipe or AI — is shown, and AI plans always require review. Language artifacts are fetched through the same SSRF-guarded URL fetcher (with a curated vendor-CDN allowlist) used elsewhere.

Learned recipes A reviewed AI plan can be promoted to a learned recipe, so the next upload of the same product (matched by normalized name + publisher) skips the LLM.
Build & deploy

Building the .intunewin

Once the profile is approved, Wrappify assembles a clean source folder (setup file + companions) and builds the package. On Windows it uses Microsoft's IntuneWinAppUtil.exe; elsewhere it uses the cross-platform SvRooij.ContentPrep PowerShell module.

IntuneWinAppUtil.exe -c <source_folder> -s <setup_file> -o <output_folder> -q
  • -q (quiet) is mandatory — otherwise the tool is interactive.
  • <setup_file> is the filename only, not a path.
  • The source folder contains only files meant for the package; the output is <setup_file_without_ext>.intunewin.

Deploying to Intune

Deploy uploads the built package to your tenant's Intune through the Microsoft Graph API, performing the full Win32 app upload dance:

  1. create the Win32LobApp from the profile;
  2. create a content version and the file entry;
  3. upload the encrypted content to Azure Blob storage;
  4. commit the content version;
  5. optionally set a supersedence relationship and assignment-group intents.

Graph calls use the client-credentials flow against the customer's tenant, with a per-tenant token cache. It requires DeviceManagementApps.ReadWrite.All consent (granted at onboarding).

Sharp edges Intune's Graph surface has many quirks (assignment-intent shapes, architecture fields, supersedence restrictions). These are documented in the project's docs/intune-graph-gotchas.md so they don't have to be relearned.

Version management via supersedence

Re-upload a newer version and Wrappify matches it to the previous app and offers to set up Intune supersedence, so devices update cleanly instead of getting a duplicate.

How the previous version is matched

  • MSI — by UpgradeCode (a strong match).
  • EXE — by publisher + normalized-name similarity (fuzzy match).

The Supersedence panel pre-checks the best previous version; you confirm before deploy. When importing from the catalog, candidates can be auto-matched and pre-selected.

Enterprise App Catalog apps can't be superseded Intune rejects a supersedence relationship between an uploaded app and an Enterprise App Catalog / auto-update-catalog app. Wrappify excludes those from candidates and, on the Deployed page, offers a manual update path plus Remove / Unassign actions instead.

Assignment groups

On deploy you can opt in to have Wrappify provision the Entra ID security groups an admin would otherwise hand-create, and set the matching Intune assignment intent for each. For an app named Microsoft Teams:

Group (display name)Assignment intentEffect
app - Microsoft Teams (available)availableSelf-service in Company Portal
app - Microsoft Teams (required)requiredAuto-installed on members' devices
app - Microsoft Teams (uninstall)uninstallRemoves the app from members' devices
  • Idempotent — groups are looked up by display name and reused; never duplicated on re-deploy.
  • Configurable naming — the prefix and suffix come from settings, so you can match your taxonomy.
  • Security-group only, created in the customer's tenant via that tenant's token.
  • Best-effort — a Graph failure (e.g. missing Group.ReadWrite.All consent) is logged but does not fail the deploy; the app is already live.
  • Persisted — created group IDs are stored so they can be cleaned up later, and re-deploys surface the existing groups.

Deployed apps

The Deployed page lists your tenant's Intune Win32 apps (read from Graph, with a database fallback when Graph is down) and groups them into families by real Intune supersedence — each app's relationships are folded into a directed graph so the newest version is the head and older/superseded versions collapse beneath it.

Each app is correlated with the App Catalog to show:

  • a from-catalog tag when it was imported from the catalog;
  • the best-matching published entry and its version;
  • an update-available flag when the catalog has a newer version — one click imports it and drops into review for supersedence.

For apps that can't be superseded (Enterprise App Catalog apps), the page offers Remove from Intune (Graph delete) and the gentler Unassign (keeps the app, stops it installing). Deleting a Wrappify-deployed app also deletes the Entra assignment groups Wrappify created for it (reused/pre-existing groups are left alone).

Sharing & admin

App Catalog

The App Catalog shares pre-built .intunewin packages across tenants. An operator reviews submissions through a pending → published lifecycle; tenants browse published entries and deploy them with one click.

Import = staging

Importing an entry materializes its .intunewin into your tenant's build storage and creates a normal upload (plus a succeeded build and the carried-over icon). From there you use the standard review → deploy flow — supersedence, dependencies, assignment groups, profile and icon all apply.

Status & verification

  • The catalog badge reads Imported (a staged upload exists) or Deployed (the upload's Intune app ID is set) — not a bare "used" flag.
  • Verify reconciles against Intune: if the app was deleted in the portal, the stored app ID is cleared so the badge flips back and a redeploy is possible.
  • Auto-supersedence on import — Wrappify matches the entry against your existing Intune apps (MSI UpgradeCode, else publisher + normalized-name similarity) and pre-selects the best previous version.

Per-architecture variants & icons

Same-product entries are grouped into one card with an architecture selector (x64 / arm64 / x86). Icons are resolved once at entry creation, stored on the row and reused on deploy. An upload whose SHA-256 matches a published entry short-circuits the LLM call and pre-fills the profile.

Blob storage backend

The blob backend is pluggable, selected by CATALOG_BLOB_ENDPOINT: a local filesystem for dev / single-host, or an S3-compatible object store — Cloudflare R2, AWS S3 or MinIO — for production. Both use identical blob keys, so moving from filesystem to object store needs no database rewrite. On the object-store backend, downloads 307-redirect to presigned URLs (TTL via CATALOG_BLOB_URL_TTL) so bytes flow straight from R2/S3; the local backend streams them server-side. Deploys always materialize the package server-side first.

Multi-tenancy & onboarding

Wrappify is multi-tenant from day one. Tenant data never lives in environment variables — those hold only the app's own configuration.

Onboarding

Wrappify is registered as a single multi-tenant Entra ID app. Customer tenants onboard via the standard admin-consent URL. End users sign in with OIDC; their tid claim is matched against the tenant table. An unknown tenant sees a "not onboarded" page with the consent link.

Graph permissions

PermissionWhy
DeviceManagementApps.ReadWrite.AllUpload & manage Win32 apps
User.ReadBasic profile for sign-in
Group.ReadWrite.AllOptional — only for assignment groups
Organization.Read.AllOptional — read the tenant's display name

Isolation rules

  • Every tenant-scoped query filters by tenant_id, enforced at the database layer (a SQLAlchemy event listener), not by developer discipline.
  • Object-storage paths are namespaced per tenant.
  • The LLM cache is per tenant — suggestions are never shared across tenants, even for identical file hashes.

Offboarding

Settings → Danger zone offers self-service offboarding: it blocks access and permanently deletes the tenant's Wrappify data (uploads, builds, profiles, LLM cache, settings) and storage. Deployed Intune apps and Wrappify-created Entra groups are left in place.

Settings

The per-tenant Settings page lets each tenant configure:

  • LLM provider — Anthropic (recommended), OpenAI, xAI Grok or Google Gemini.
  • API key — the tenant's own key, stored Fernet-encrypted and write-only (the API never returns the key, only whether one is set).
  • Model override.
  • Group-name prefix for assignment groups.

Any field left empty falls back to the global default. The encryption key (SETTINGS_ENCRYPTION_KEY) lives in the environment, never in the database; if it's unconfigured the system refuses to store a key rather than fall back to plaintext.

Operator admin

The operator admin area (/admin) lives outside the Entra sign-in gates and is protected by a single local ADMIN_TOKEN sent as an X-Admin-Token header (constant-time compared, never logged). When the token is unset, the entire admin API returns 404 — no attack surface by default.

From there an operator can:

  • list and oversee all tenants;
  • edit any tenant's settings (set/clear the API key — but never read it);
  • suspend, reactivate or offboard a tenant;
  • review the App Catalog — see cross-tenant uploads, promote/publish/reject entries and refresh icons.
Reference

Security model

  • Installers are never executed — Wrappify reads metadata only, not even for testing.
  • Upload validation — extension, magic bytes and a max size; the build tool runs in an isolated workdir that's deleted afterward.
  • Per-tenant secrets never live in env vars; the per-tenant LLM key is encrypted at rest and write-only.
  • The LLM API key stays in the backend — it is never exposed to the frontend.
  • Tenant isolation is enforced at the database layer.
  • Least-privilege Graph — client-credentials flow, the extra group scope gated behind an opt-in toggle.
  • SSRF-guarded URL fetching for the add-from-URL and localization features.
  • Optional virus scanning — every upload can be scanned (ClamAV / VirusTotal) before it is stored; a confirmed detection hard-blocks the file.
Deep inspection sandbox Running an installer to observe its real behavior is a separate, opt-in flow designed to run outside this trust boundary in an ephemeral Windows sandbox. It is not part of the main backend.

Reference & limits

Accepted inputs

  • Single installer: .exe, .msi, .msp.
  • .zip of an installer folder (single app or one-app-per-architecture).
  • Any of the above fetched from a URL (when enabled).
  • Companion files: a wider payload set than the primary upload.

Caps (operator-configurable)

SettingBounds
MAX_UPLOAD_SIZE_MBMax upload / URL-fetch size (default 1024)
MAX_ARCHIVE_FILESMax files extracted from a ZIP (default 200)
MAX_ARCHIVE_EXTRACTED_MBMax extracted payload (default 2048)
URL_UPLOAD_ENABLEDMaster switch for server-side URL fetch
VIRUS_SCAN_ENABLEDOptional upload virus scanning (ClamAV / VirusTotal)

Where to go next

  • Open the app and package your first installer.
  • Read the FAQ on the landing page.
  • Deeper engineering docs live in the repository: docs/architecture.md, docs/intune-graph-gotchas.md, docs/app-catalog.md, docs/entra-setup.md and docs/deep-inspection.md.