Public/New-IntuneAppIdMap.ps1

# Auto-generated by module/Build-Module.ps1 from en/scripts/Build-IntuneAppIdMap.ps1
# Edit the source script, then re-run Build-Module.ps1. Do not edit this file directly.

function New-IntuneAppIdMap {
<#
.SYNOPSIS
    Builds a SourceId to TargetId app mapping CSV for Intune app configuration imports.

.DESCRIPTION
    App configuration policies reference mobileApp IDs from the source tenant. Those IDs are not valid in
    the target tenant. This script reads exported apps, connects to the target tenant, matches target apps
    primarily by displayName and @odata.type, and outputs AppIdMap.csv with SourceId, TargetId, DisplayName,
    SourceType, TargetType, MatchStatus.
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory=$true)][string]$SourcePath,
    [Parameter(Mandatory=$true)][string]$TargetTenantId,
    [string]$OutputCsv = (Join-Path (Get-Location) 'AppIdMap.csv')
)
$ErrorActionPreference='Stop'
$GraphBase='https://graph.microsoft.com/beta'
$Scopes=@('DeviceManagementApps.Read.All','DeviceManagementApps.ReadWrite.All')


function Connect-GraphForIntuneAutomation {
    param(
        [Parameter(Mandatory=$true)][string]$TenantId,
        [string[]]$Scopes
    )
    Import-Module Microsoft.Graph.Authentication -ErrorAction Stop

    $tenantLower = $TenantId.ToLowerInvariant()
    $sourceTenant = $env:INTUNE_AUTO_SOURCE_TENANT_ID
    $targetTenant = $env:INTUNE_AUTO_TARGET_TENANT_ID

    $clientId = $null
    $certThumb = $null

    if ($sourceTenant -and ($tenantLower -eq $sourceTenant.ToLowerInvariant())) {
        $clientId = $env:INTUNE_AUTO_SOURCE_CLIENT_ID
        $certThumb = $env:INTUNE_AUTO_SOURCE_CERT_THUMBPRINT
    }
    if ($targetTenant -and ($tenantLower -eq $targetTenant.ToLowerInvariant())) {
        $clientId = $env:INTUNE_AUTO_TARGET_CLIENT_ID
        $certThumb = $env:INTUNE_AUTO_TARGET_CERT_THUMBPRINT
    }
    if (-not $clientId -and $env:INTUNE_AUTO_CLIENT_ID) { $clientId = $env:INTUNE_AUTO_CLIENT_ID }
    if (-not $certThumb -and $env:INTUNE_AUTO_CERT_THUMBPRINT) { $certThumb = $env:INTUNE_AUTO_CERT_THUMBPRINT }

    $ctx = Get-MgContext -ErrorAction SilentlyContinue

    if ($clientId -and $certThumb) {
        if ($ctx -and $ctx.TenantId -and ($ctx.TenantId.ToLowerInvariant() -eq $tenantLower) -and $ctx.ClientId -and ($ctx.ClientId -eq $clientId)) {
            return
        }
        Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
        Connect-MgGraph -TenantId $TenantId -ClientId $clientId -CertificateThumbprint $certThumb -ContextScope Process -NoWelcome | Out-Null
        return
    }

    if ($ctx -and $ctx.TenantId -and ($ctx.TenantId.ToLowerInvariant() -eq $tenantLower)) {
        $ctxScopes = @($ctx.Scopes)
        $missing = @()
        foreach ($s in @($Scopes)) {
            if ($ctxScopes -notcontains $s) { $missing += $s }
        }
        if ($missing.Count -eq 0) { return }
        Write-Host ("Missing Graph scopes for {0}: {1}. Re-authentication required." -f $TenantId, ($missing -join ', ')) -ForegroundColor Yellow
        Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
    } elseif ($ctx) {
        Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
    }

    Connect-MgGraph -TenantId $TenantId -Scopes $Scopes -ContextScope Process -NoWelcome | Out-Null
}

function Resolve-ExportRoot($Path){
 if(-not (Test-Path -LiteralPath $Path)){ throw "Source not found: $Path" }
 $item=Get-Item -LiteralPath $Path
 if($item.PSIsContainer){
  if(Test-Path -LiteralPath (Join-Path $item.FullName 'manifest.json')){ return $item.FullName }
  $mf=Get-ChildItem -Path $item.FullName -Filter manifest.json -Recurse -File | Select-Object -First 1
  if($mf){ return $mf.Directory.FullName }
 }
 if($item.Extension -ieq '.zip'){
  $tmp=Join-Path $env:TEMP ('IntuneMap_' + [guid]::NewGuid().Guid)
  New-Item -Path $tmp -ItemType Directory -Force | Out-Null
  Expand-Archive -Path $item.FullName -DestinationPath $tmp -Force
  $mf=Get-ChildItem -Path $tmp -Filter manifest.json -Recurse -File | Select-Object -First 1
  if($mf){ return $mf.Directory.FullName }
 }
 throw 'manifest.json not found.'
}
function Get-AllGraphItems($Uri){
 $all=@(); $next="$GraphBase/$Uri"
 do{
  $resp=Invoke-MgGraphRequest -Method GET -Uri $next
  if($resp.value){$all += @($resp.value)} elseif($resp.id){$all += $resp}
  $next=$resp.'@odata.nextLink'
 }while($next)
 return $all
}
function Read-Json($Path){ Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json }
function Get-Prop($Obj,$Name){ if($Obj.PSObject.Properties.Name -contains $Name){ return $Obj.PSObject.Properties[$Name].Value }; return $null }
try{
 $root=Resolve-ExportRoot $SourcePath
 $appFolder=Join-Path $root '09_Apps'
 if(-not (Test-Path -LiteralPath $appFolder)){ throw '09_Apps folder missing.' }
 Import-Module Microsoft.Graph.Authentication -ErrorAction Stop
 Connect-GraphForIntuneAutomation -TenantId $TargetTenantId -Scopes $Scopes
 $targetApps=@(Get-AllGraphItems -Uri 'deviceAppManagement/mobileApps')
 $rows=@()
 foreach($f in Get-ChildItem -Path $appFolder -Filter '*.json' -File){
  $src=Read-Json $f.FullName
  $sid=$src.id; $name=$src.displayName; $stype=$src.'@odata.type'
  $matches=@($targetApps | Where-Object { (Get-Prop $_ 'displayName') -eq $name -and (Get-Prop $_ '@odata.type') -eq $stype })
  if($matches.Count -eq 1){
   $rows += [pscustomobject]@{SourceId=$sid;TargetId=(Get-Prop $matches[0] 'id');DisplayName=$name;SourceType=$stype;TargetType=(Get-Prop $matches[0] '@odata.type');MatchStatus='ExactNameAndType'}
  } elseif($matches.Count -gt 1){
   $rows += [pscustomobject]@{SourceId=$sid;TargetId='';DisplayName=$name;SourceType=$stype;TargetType='';MatchStatus="MultipleMatches:$($matches.Count)"}
  } else {
   $byName=@($targetApps | Where-Object { (Get-Prop $_ 'displayName') -eq $name })
   if($byName.Count -eq 1){
    $rows += [pscustomobject]@{SourceId=$sid;TargetId=(Get-Prop $byName[0] 'id');DisplayName=$name;SourceType=$stype;TargetType=(Get-Prop $byName[0] '@odata.type');MatchStatus='NameOnly'}
   } else {
    $rows += [pscustomobject]@{SourceId=$sid;TargetId='';DisplayName=$name;SourceType=$stype;TargetType='';MatchStatus='NoTargetMatch'}
   }
  }
 }
 $rows | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8
 Write-Host "Mapping generated: $OutputCsv" -ForegroundColor Green
}catch{
 Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
 throw
}
}