Public/DeploymentOrchestration.ps1

# DeploymentOrchestration.ps1
# Main orchestration function for deploying Winget apps to Intune

function Deploy-WinGetApp {
    <#
    .SYNOPSIS
    Deploys a Winget application to Intune as a Win32 app with proactive remediation.
    .DESCRIPTION
    Orchestrates the full deployment workflow: creates directory, groups, scripts,
    proactive remediation, IntuneWin package, uploads to Intune, and assigns groups.
    #>

    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory = $true)] [string]$AppId,
        [Parameter(Mandatory = $true)] [string]$AppName,
        [Parameter(Mandatory = $true)] [string]$BasePath,
        [string]$InstallGroupName,
        [string]$UninstallGroupName,
        [ValidateSet("Device", "User", "Both", "None")] [string]$AvailableInstall = "None",
        [switch]$Force,
        [bool]$Remediation = $true
    )

    Write-Host "========== Deploying $AppName ($AppId) ==========" -ForegroundColor Cyan
    Write-Verbose "Starting deployment for $AppName ($AppId)"

    # Check if app already exists
    $existingApp = Test-ExistingIntuneApp -AppName $AppName
    if ($existingApp.Exists) {
        Write-Host "WARNING: App '$AppName' already exists in Intune:" -ForegroundColor Yellow
        foreach ($app in $existingApp.Apps) {
            Write-Host " - $($app.displayName) [ID: $($app.id)]" -ForegroundColor Yellow
        }
        
        if (-not $Force) {
            Write-Host "Skipping deployment. Use -Force parameter to override." -ForegroundColor Yellow
            Write-Host "========== Skipped $AppName ==========" -ForegroundColor Yellow
            return
        }

        # -Force: remove existing app(s) and remediation before re-creating
        Write-Host "Force parameter detected. Removing existing resources before redeployment..." -ForegroundColor Green
        foreach ($app in $existingApp.Apps) {
            try {
                Invoke-MgGraphRequest -Uri "beta/deviceAppManagement/mobileApps/$($app.id)" -Method DELETE -ErrorAction Stop | Out-Null
                Write-Host " Removed existing app: $($app.displayName) [$($app.id)]" -ForegroundColor Yellow
            } catch {
                Write-Warning " Failed to remove app $($app.id): $_"
            }
        }

        # Remove existing proactive remediation to avoid duplicates
        $remediationName = "$AppName Proactive Update"
        $escapedRemName = $remediationName.Replace("'", "''")
        $remFilter = [uri]::EscapeDataString("displayName eq '$escapedRemName'")
        try {
            $existingRems = (Invoke-MgGraphRequest -Uri "beta/deviceManagement/deviceHealthScripts?`$filter=$remFilter" -Method GET -ErrorAction Stop).value
            foreach ($rem in $existingRems) {
                Invoke-MgGraphRequest -Uri "beta/deviceManagement/deviceHealthScripts/$($rem.id)" -Method DELETE -ErrorAction Stop | Out-Null
                Write-Host " Removed existing remediation: $($rem.displayName)" -ForegroundColor Yellow
            }
        } catch {
            Write-Verbose "Could not check/remove existing remediations: $_"
        }
    }

    # 1. Create app directory
    $appPath = Join-Path $BasePath $AppId
    New-Item -Path $appPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
    Write-Verbose "Created app directory: $appPath"

    # 2. Create/get groups
    $installGroupId = Get-OrCreateAADGroup -AppId $AppId -AppName $AppName -GroupType "Install" -GroupName $InstallGroupName
    $uninstallGroupId = Get-OrCreateAADGroup -AppId $AppId -AppName $AppName -GroupType "Uninstall" -GroupName $UninstallGroupName

    # 3. Create scripts
    # Sanitize AppId for filenames - remove special characters that cause issues
    $safeAppId = $AppId -replace '[^a-zA-Z0-9._-]', '_'

    $installFilename = "install$safeAppId.ps1"
    $installScriptFile = Join-Path $appPath $installFilename
    New-WinGetScript -AppId $AppId -AppName $AppName -ScriptType "Install" | Out-File $installScriptFile -Encoding utf8
    Write-Verbose "Created: $installScriptFile"

    $uninstallFilename = "uninstall$safeAppId.ps1"
    $uninstallScriptFile = Join-Path $appPath $uninstallFilename
    New-WinGetScript -AppId $AppId -AppName $AppName -ScriptType "Uninstall" | Out-File $uninstallScriptFile -Encoding utf8
    Write-Verbose "Created: $uninstallScriptFile"

    $detectionFilename = "detection$safeAppId.ps1"
    $detectionScriptFile = Join-Path $appPath $detectionFilename
    New-WinGetScript -AppId $AppId -AppName $AppName -ScriptType "DetectionRemediation" | Out-File $detectionScriptFile -Encoding utf8
    Write-Verbose "Created: $detectionScriptFile"

    # 4. Create proactive remediation (if enabled and licensed)
    if ($Remediation -and (Test-ProactiveRemediationLicense)) {
        New-ProactiveRemediation -AppId $AppId -AppName $AppName -GroupId $installGroupId | Out-Null
    } elseif (-not $Remediation) {
        Write-Host "Skipping Proactive Remediation creation - disabled for this app" -ForegroundColor Yellow
    } else {
        Write-Host "Skipping Proactive Remediation creation - not licensed" -ForegroundColor Yellow
    }

    # 5. Create IntuneWin package
    # The IntuneWin file will be created without the .ps1 extension in the name
    $intunewinFilename = $installFilename -replace '\.ps1$', ''
    $intunewinPath = Join-Path $BasePath "$intunewinFilename.intunewin"
    New-IntuneWinFile -appid $AppId -appname $AppName -apppath $appPath -setupfilename $installFilename -destpath $BasePath *>&1 | Out-Null
    Write-Verbose "Created: $intunewinPath"

    # Brief pause for file system
    Start-Sleep -Seconds 2

    # 5.5. Search for app icon
    $appIcon = Get-AppIcon -AppId $AppId -AppName $AppName

    # 6. Upload Win32 app
    $installCmd = "powershell.exe -ExecutionPolicy Bypass -File $installFilename"
    $uninstallCmd = "powershell.exe -ExecutionPolicy Bypass -File $uninstallFilename"

    try {
        $appUploadResult = New-Win32App -appid $AppId -appname $AppName -appfile $intunewinPath -installcmd $installCmd -uninstallcmd $uninstallCmd -detectionfile $detectionScriptFile -largeIcon $appIcon
        
        if (-not $appUploadResult) {
            throw "Upload returned null - no app was created in Intune"
        }

        Write-Host "Uploaded $AppName to Intune successfully" -ForegroundColor Green
            
        # 7. Assign groups
        Grant-Win32AppAssignment -AppName $AppName -InstallGroupId $installGroupId -UninstallGroupId $uninstallGroupId -AvailableInstall $AvailableInstall
        Write-Host "Assigned groups to $AppName"
    }
    catch {
        Write-Error "Error uploading $AppName to Intune: $_"
        throw
    }

    Write-Host "========== Completed $AppName ==========" -ForegroundColor Green
    Write-Verbose "Completed deployment for $AppName"
}