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.
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:
- extracts deterministic metadata (publisher, product, version, MSI ProductCode, PE VersionInfo, architecture, installer-type heuristics);
- asks an LLM to propose a complete install profile from that metadata;
- lets you review and edit every field in the browser;
- builds the
.intunewinpackage; and - deploys it to your tenant's Intune via Microsoft Graph — optionally with supersedence relationships and Entra ID assignment groups.
Core concepts
| Term | What it means in Wrappify |
|---|---|
| Installer profile | The 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 rule | How Intune decides whether the app is already installed. Exactly one per app: MSI Product Code, Registry, File, or PowerShell script. |
.intunewin | Microsoft's encrypted package format for Win32 apps, produced by IntuneWinAppUtil.exe (or the cross-platform ContentPrep module). |
| Win32LobApp | The Microsoft Graph resource type Wrappify creates when it deploys your app to Intune. |
| Supersedence | An Intune relationship that marks a new app version as replacing an older one, so devices update cleanly. |
| Tenant | A customer Microsoft Entra ID organization. All data is strictly isolated per tenant. |
| Companion file | Any extra file that must ship inside the package alongside the main installer. |
Quick start
- 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.
- Set your LLM key. Go to Settings and add your API key for Anthropic (recommended), OpenAI, Grok or Gemini. See Settings.
- Upload an installer. Drop an
.exe,.msi,.msp, or a.zipof a whole installer folder. You can also paste a URL. - Review the proposal. Wrappify inspects the file and the LLM fills in the profile. Edit anything that's off.
- 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:
Inspection is deterministic and never executes the installer. The LLM is called at most once per file (plus up to two schema-validation retries), and its output is cached by file hash, scoped per tenant.
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.
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/installnames; a penalty forunins,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.Machineas 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.
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-Dispositionor the URL path).
The whole feature is gated by a master switch (URL_UPLOAD_ENABLED); when
off, the endpoints return 404.
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
clamddaemon for anINSTREAMscan. - 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.
Selected by VIRUS_SCAN_BACKEND and gated by VIRUS_SCAN_ENABLED
(off → no behaviour change); fail-mode via VIRUS_SCAN_FAIL_CLOSED.
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:
| Field | Source / value |
|---|---|
| Identity | ProductCode, ProductName, ProductVersion, Manufacturer, UpgradeCode |
| Silent install | msiexec /i "<file>.msi" /qn /norestart |
| Silent uninstall | msiexec /x <ProductCode> /qn /norestart |
| Detection | MSI 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 type | Typical 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 |
Detected types: Inno, NSIS, InstallShield, WiX Burn, Squirrel, MSI-wrapped EXE and the New Teams bootstrapper. Unknown EXEs are flagged so the LLM proposes the most likely combination and explains its reasoning.
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
InstallerProfileschema, 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.mdand 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:
- MSI Product Code — MSI only; natively supported by Intune.
- Registry key — the
HKLM\…\Uninstall\key (orWOW6432Node) withDisplayName+ aDisplayVersion ≥ Xcomparison. - File presence — with an optional version check.
- PowerShell detection script — only when 1–3 don't fit. Supported by the model and editable in the UI.
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.
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 deletefor.regfiles), rewriting the uninstall command too.
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.xpilangpacks 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.
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.
The build runs in an isolated workdir that is deleted afterwards. Build history is recorded per tenant.
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:
- create the
Win32LobAppfrom the profile; - create a content version and the file entry;
- upload the encrypted content to Azure Blob storage;
- commit the content version;
- 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).
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.
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 intent | Effect |
|---|---|---|
app - Microsoft Teams (available) | available | Self-service in Company Portal |
app - Microsoft Teams (required) | required | Auto-installed on members' devices |
app - Microsoft Teams (uninstall) | uninstall | Removes 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.Allconsent) 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.
This feature needs the extra Group.ReadWrite.All scope, so it's opt-in via
a per-deploy toggle (default off).
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).
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.
Tenant contributions are opt-in (CATALOG_CONTRIBUTIONS_ENABLED); pending
entries auto-expire after a configurable TTL.
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
| Permission | Why |
|---|---|
DeviceManagementApps.ReadWrite.All | Upload & manage Win32 apps |
User.Read | Basic profile for sign-in |
Group.ReadWrite.All | Optional — only for assignment groups |
Organization.Read.All | Optional — 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.
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.
Reference & limits
Accepted inputs
- Single installer:
.exe,.msi,.msp. .zipof 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)
| Setting | Bounds |
|---|---|
MAX_UPLOAD_SIZE_MB | Max upload / URL-fetch size (default 1024) |
MAX_ARCHIVE_FILES | Max files extracted from a ZIP (default 200) |
MAX_ARCHIVE_EXTRACTED_MB | Max extracted payload (default 2048) |
URL_UPLOAD_ENABLED | Master switch for server-side URL fetch |
VIRUS_SCAN_ENABLED | Optional 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.mdanddocs/deep-inspection.md.