Public/New-IntuneWingetPackage.ps1

<#
.SYNOPSIS
    Converts a Winget package into a compiled Microsoft Intune Win32 App package (.intunewin).
.DESCRIPTION
    Uses real Winget manifest metadata, type-safe package adapters, and official IntuneWinAppUtil
    to compile production-ready Intune Win32 application packages.
.PARAMETER PackageId
    The Winget package identifier (e.g., 'Git.Git', 'Google.Chrome').
.PARAMETER OutputFolder
    Destination folder for the compiled .intunewin and script assets.
.PARAMETER CustomArgs
    Additional custom arguments to pass to installer.
.PARAMETER Scope
    Installation scope: 'machine' (default for Intune) or 'user'.
.PARAMETER Publish
    Immediately publish the compiled package to Microsoft Intune via Microsoft Graph.
.PARAMETER AssignTo
    Entra ID group names or IDs to assign the published app to.
.PARAMETER Intent
    Assignment intent: 'Required' (default) or 'Available'.
.EXAMPLE
    New-IntuneWingetPackage -PackageId "Git.Git" -OutputFolder ".\dist"
#>

function New-IntuneWingetPackage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$PackageId,

        [Parameter()]
        [string]$DisplayName = '',

        [Parameter()]
        [string]$OutputFolder = '',

        [Parameter()]
        [string]$CustomArgs = '',

        [Parameter()]
        [ValidateSet('machine', 'user')]
        [string]$Scope = 'machine',

        [Parameter()]
        [switch]$Publish,

        [Parameter()]
        [string[]]$AssignTo = @(),

        [Parameter()]
        [ValidateSet('Required', 'Available')]
        [string]$Intent = 'Required'
    )

    Write-Host "`n [WingetIntune] Enterprise Win32 App Packager" -ForegroundColor Cyan
    Write-Host " --------------------------------------------" -ForegroundColor DarkGray

    # 1. Fetch Manifest and Resolve Adapter
    $manifest = Get-WingetManifest -PackageId $PackageId
    $adapter = Get-PackageAdapter -InstallerType $manifest.InstallerType -Manifest $manifest

    if (-not $DisplayName) {
        $DisplayName = if ($manifest.Name) { $manifest.Name } else { $PackageId }
    }

    if (-not $OutputFolder) {
        $OutputFolder = Join-Path (Get-Location) ("IntunePackages\" + $PackageId)
    }

    $srcFolder = Join-Path $OutputFolder 'src'
    if (-not (Test-Path $srcFolder)) {
        New-Item -ItemType Directory -Path $srcFolder -Force | Out-Null
    }

    # 2. Generate Install.ps1
    $adapterType = $adapter.InstallerType
    Write-Host " [+] Generating self-healing SYSTEM install shim for $adapterType adapter..." -ForegroundColor Cyan
    $effectiveArgs = if ($CustomArgs) { $adapter.SilentArgs + " " + $CustomArgs } else { $adapter.SilentArgs }
    $installCode = New-StandaloneInstallShim -PackageId $PackageId -CustomArgs $effectiveArgs -Scope $Scope -SuccessCodes $adapter.SuccessCodes
    $installPath = Join-Path $srcFolder 'Install.ps1'
    $installCode | Out-File -FilePath $installPath -Force -Encoding utf8

    # 3. Generate Uninstall.ps1
    Write-Host " [+] Generating uninstall script..." -ForegroundColor Cyan
    $uninstallLines = @(
        "# Standalone Uninstall Script for $PackageId",
        "`$wingetCmd = Get-Command 'winget.exe' -ErrorAction SilentlyContinue",
        "`$wingetPath = if (`$wingetCmd) { `$wingetCmd.Source } else { 'winget.exe' }",
        "& `$wingetPath uninstall --exact --id '$PackageId' --silent --disable-interactivity",
        "exit `$LASTEXITCODE"
    )
    $uninstallPath = Join-Path $srcFolder 'Uninstall.ps1'
    $uninstallLines | Out-File -FilePath $uninstallPath -Force -Encoding utf8

    # 4. Generate Detect.ps1
    Write-Host " [+] Generating detection script..." -ForegroundColor Cyan
    $detectCode = New-StandaloneDetectShim -PackageId $PackageId -DisplayName $DisplayName -ProductCode $adapter.ProductCode -MinVersion $manifest.Version -DetectionStrategy $adapter.DetectionStrategy
    $detectPath = Join-Path $OutputFolder 'Detect.ps1'
    $detectCode | Out-File -FilePath $detectPath -Force -Encoding utf8

    # 5. Fetch IntuneWinAppUtil and Compile .intunewin
    $utilPath = Get-IntuneWinAppUtil
    Write-Host " [+] Compiling $PackageId.intunewin package..." -ForegroundColor Cyan
    
    $proc = Start-Process -FilePath $utilPath -ArgumentList "-c `"$srcFolder`" -s `"Install.ps1`" -o `"$OutputFolder`" -q" -Wait -PassThru -NoNewWindow
    
    $intuneWinFile = Join-Path $OutputFolder "Install.intunewin"
    $renamedIntuneWin = Join-Path $OutputFolder ($PackageId + ".intunewin")

    if (Test-Path $intuneWinFile) {
        Move-Item -Path $intuneWinFile -Destination $renamedIntuneWin -Force
    }

    if (-not (Test-Path $renamedIntuneWin)) {
        throw "Packaging failed. IntuneWinAppUtil did not produce an output package."
    }

    $packageSizeMb = [Math]::Round((Get-Item $renamedIntuneWin).Length / 1MB, 2)
    Write-Host " [OK] Package compiled: $renamedIntuneWin ($packageSizeMb MB)" -ForegroundColor Green

    # 6. Emit Metadata JSON
    $metadata = [PSCustomObject]@{
        PackageId            = $PackageId
        DisplayName          = $DisplayName
        Publisher            = $manifest.Publisher
        InstallerType        = $adapter.InstallerType
        DetectionStrategy    = $adapter.DetectionStrategy
        InstallCommandLine   = 'powershell.exe -ExecutionPolicy Bypass -File .\Install.ps1'
        UninstallCommandLine = 'powershell.exe -ExecutionPolicy Bypass -File .\Uninstall.ps1'
        IntuneWinPath        = $renamedIntuneWin
        DetectionScriptPath  = $detectPath
        PackageSizeMb        = $packageSizeMb
        CompiledAt           = (Get-Date).ToString('o')
    }

    $metaPath = Join-Path $OutputFolder 'IntuneAppMetadata.json'
    $metadata | ConvertTo-Json -Depth 5 | Out-File -FilePath $metaPath -Force -Encoding utf8
    Write-Host " [OK] Metadata generated: $metaPath" -ForegroundColor Green

    # 7. Publish if requested
    if ($Publish) {
        return (Publish-IntuneWingetApp -IntuneWinPath $renamedIntuneWin -MetadataJsonPath $metaPath -AssignTo $AssignTo -Intent $Intent)
    }

    return $metadata
}