A Developer Kit for Intune Company Portal

NOTE: Updated with Claude Desktop app too

The dev manager wanted Claude Code easily available on the new Autopilot laptops. The repo is at haakonwibe/claude-code-intune. Five Win32 apps assigned to a single Entra “Developers” group: Claude Code, Claude Desktop, Git for Windows, VS Code, and PowerShell 7. Each app is assigned on its own, with no dependencies between them.

Claude Code

Of the CLI tools, Claude Code is slightly more interesting. Git, VS Code, and PowerShell 7 are vendor installers run silently. The wrapper just runs Anthropic’s bootstrap script (irm https://claude.ai/install.ps1 | iex), with two additions.

The bootstrap’s output is piped into the same CMTrace log the wrapper writes to, so debugging the install only needs one file. And the wrapper adds %USERPROFILE%\.local\bin to HKCU\Environment\Path, since the bootstrap doesn’t set PATH on Windows.

The bootstrap keeps the binary updated in the background, and the wrapper stays out of its way.

User-context Win32

Most CLI tools deployed through Intune use system context. Anthropic’s bootstrap is per-user, so this kit configures the Win32 app for user-context install instead. It’s a less common path, often skipped because people assume it doesn’t work cleanly through Intune, but it does.

sysnative

The bootstrap needs to run in 64-bit PowerShell. IME runs as a 32-bit process, so a script it starts runs in 32-bit PowerShell too. Calling the bootstrap from there fails.

sysnative is the trick. From a 32-bit process on 64-bit Windows, C:\Windows\System32 is redirected down to the 32-bit binaries under SysWOW64. The virtual folder C:\Windows\sysnative is the escape hatch back to the real 64-bit System32. Re-launching PowerShell from there gives a 64-bit shell.

The wrapper does this at the top:

if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) {
    $sysnative = Join-Path $env:SystemRoot 'sysnative\WindowsPowerShell\v1.0\powershell.exe'
    & $sysnative -ExecutionPolicy Bypass -File $PSCommandPath @args
    exit $LASTEXITCODE
}

Logs

User-context logs go to %LOCALAPPDATA%\Hawkweave\ClaudeCodeIntune\Logs. System-context logs go to %ProgramData%\Hawkweave\ClaudeCodeIntune\Logs. CMTrace format throughout. Open them in CMTrace or OneTrace from the ConfigMgr toolkit.

Detection

Per-user installs have a structural problem with Intune detection: Win32 detection always runs as SYSTEM, but the binary lives under a specific user profile. SYSTEM can’t pick the right profile, and walking C:\Users\* isn’t always pretty.

One way is to make the install script write a marker file to a system-wide path, and have the uninstall script remove it. Detection is then a single Test-Path. The pattern works for any per-user tool pushed through Intune.

$markerPath = 'C:\ProgramData\Hawkweave\ClaudeCodeIntune\Markers\ClaudeCode.tag'

if (Test-Path $markerPath) {
    Write-Output "Claude Code detected (marker: $markerPath)"
    exit 0
}
exit 0

It only proves the wrapper ran successfully of course, with exit code 0, not that Claude Code itself works.

Claude Desktop

Claude Desktop ships as an MSIX, and Add-AppxPackage doesn’t always play well for it.

The package contains a service, and Add-AppxPackage can fail with an error code 0x80073D28. The only working install path I found to be predictable is Add-AppxProvisionedPackage, which provisions the package for all users. Existing user sessions should pick it up automatically, as do new logons.

The wrapper drives that command and handles the rest: optional Virtual Machine Platform enable for Cowork, the seven HKLM policy values Anthropic documents under HKLM:\SOFTWARE\Policies\Claude, and a detection key.

Detection by version plus policy hash

Detection writes a Version REG_SZ to HKLM:\SOFTWARE\Hawkweave\ClaudeCodeIntune\Apps\ClaudeDesktop formatted as <msix-version>+<policies-hash-short> (e.g. 1.5354.0+a3f9c2d1). The detection script reads the value and echoes it back.

Editing policies.json and rebuilding produces a new hash even when the MSIX version doesn’t change. Intune sees a different detection value and redeploys. Policy-only changes get pushed without bumping the binary or maintaining a separate config profile alongside the app.

Cowork (Virtual Machine Platform)

Claude Desktop’s Cowork sandbox needs Virtual Machine Platform enabled. The wrapper supports it through an -EnableCowork switch on the install command line, off by default.

The script checks state before doing anything. If VMP is already Enabled or EnablePending, it logs “no change” and continues. If state is Disabled, it does a hardware precheck via Win32_Processor for SLAT and VirtualizationFirmwareEnabled. Failures soft-skip: install continues, Cowork is unavailable on this device, no install error.

If precheck passes, Enable-WindowsOptionalFeature -Online -All -NoRestart enables VMP. When DISM reports RestartNeeded, the script exits 3010 rather than 0. Intune surfaces this in Company Portal as “Installed successfully, reboot required” through the Device restart behavior setting on the app.

Note: Nested-virt-enabled Hyper-V guests report SLAT = true but VirtualizationFirmwareEnabled = false. There’s no virtual-firmware “enable VT-x” toggle inside the guest, so the precheck soft-skips even though the DISM enable would actually succeed. Test the full path on physical hardware, or enable VMP manually on the VM first to put the script onto the “already enabled” branch.

Same package, different command lines

The install behavior is controlled with a parameter, so the same .intunewin can be uploaded as multiple Intune Win32 app objects with different install command lines. One for the standard install, a second with -EnableCowork, both pointing at the same content. Assign each to a different Entra group and Cowork can be piloted on a subset of devices without rebuilding or re-uploading the package.

Uninstall

MSIX uninstall has the provisioning quirk in reverse. Remove-AppxProvisionedPackage drops the provisioning entry so future logons don’t auto-install. Users who already received the app still have it, so Remove-AppxPackage -AllUsers cleans up those per-user installs. Both calls are needed.

Policy values under HKLM:\SOFTWARE\Policies\Claude are removed, but the parent key stays if other values or subkeys remain (so adjacent GPO or Settings Catalog values aren’t collateral damage). The detection key is removed entirely. A -RemoveVMP switch disables Virtual Machine Platform on uninstall, off by default because VMP is shared with WSL2, Windows Sandbox, Docker Desktop, and Hyper-V containers.

Signed installers

The build helper that downloads the installers (Git, VS Code, PowerShell 7, and the Claude Desktop MSIX) verifies each one’s Authenticode signature before placing it in the package/ folder. The Microsoft Win32 Content Prep Tool gets the same check. If a download is tampered with or returns something unsigned, the build fails before producing an .intunewin.

Polite uninstall

When Claude Code is uninstalled, the wrapper removes the binary, the PATH entry, and the marker file. It leaves %USERPROFILE%\.claude in place. That folder holds the user’s settings and history. If they reinstall a week later, they pick up where they left off.

Uninstall removes the kit, not the user’s data.


Have you packaged AI desktop apps or CLI tools for managed devices? Curious what others are doing for Cursor, GitHub Copilot CLI, or anything similar.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.