#requires -Version 5.1 <# .SYNOPSIS Interactive terminal UI for IntuneManagement headless export/import. .DESCRIPTION Prompts for action, tenant, paths, filters, object types, and toggles. Returns a PSCustomObject that Start-HeadlessIntune.ps1 consumes. Uses fzf on macOS/Linux when available; falls back to numbered menus. #> [CmdletBinding()] param() $ErrorActionPreference = "Stop" #region Helper functions function Test-FzfAvailable { return [bool](Get-Command fzf -ErrorAction SilentlyContinue) } function Show-FzfMenu { param( [Parameter(Mandatory)] [string[]]$Items, [string]$Header = "Select one", [switch]$Multi ) $argsList = @("--header=$Header") if($Multi) { $argsList += "--multi" } $selected = $Items | fzf @argsList if(-not $selected) { return $null } if($Multi) { return @($selected -split "`r?`n" | Where-Object { $_ }) } return $selected } function Show-NumberedMenu { param( [Parameter(Mandatory)] [string[]]$Items, [string]$Header = "Select one or more", [switch]$Multi ) Write-Host "`n$Header" -ForegroundColor Cyan for($i=0; $i -lt $Items.Count; $i++) { Write-Host " $($i+1). $($Items[$i])" } if($Multi) { $prompt = "Enter numbers separated by commas (e.g. 1,3,5) or 'all'" } else { $prompt = "Enter a number" } $choice = Read-Host $prompt if($choice -eq "all" -and $Multi) { return $Items } $indices = $choice -split "," | ForEach-Object { $_.Trim() } | Where-Object { $_ -match "^\d+$" } | ForEach-Object { [int]$_ - 1 } | Where-Object { $_ -ge 0 -and $_ -lt $Items.Count } if($Multi) { return $Items[$indices] | Select-Object -Unique } else { if($indices.Count -eq 0) { return $null } return $Items[$indices[0]] } } function Select-MenuItem { param( [Parameter(Mandatory)] [string[]]$Items, [string]$Header = "Select one", [switch]$Multi ) if(Test-FzfAvailable) { return Show-FzfMenu -Items $Items -Header $Header -Multi:$Multi } return Show-NumberedMenu -Items $Items -Header $Header -Multi:$Multi } function Read-YesNo { param( [string]$Prompt, [bool]$Default = $false ) $defaultChar = if($Default) { "Y" } else { "N" } $response = Read-Host "$Prompt [Y/n] (default: $defaultChar)" if([string]::IsNullOrWhiteSpace($response)) { return $Default } return $response -match "^\s*y" } function Get-DefaultSettingsPath { if($IsWindows -or $env:OS -eq "Windows_NT") { if($env:LOCALAPPDATA) { return (Join-Path $env:LOCALAPPDATA "macOS_IntuneManagement\Settings.json") } return (Join-Path $env:USERPROFILE "AppData\Local\macOS_IntuneManagement\Settings.json") } if($IsMacOS) { return (Join-Path $HOME "Library/Application Support/macOS_IntuneManagement/Settings.json") } return (Join-Path $HOME ".local/share/macOS_IntuneManagement/Settings.json") } #endregion #region Load defaults $modulePath = Join-Path (Split-Path -Parent $PSScriptRoot) "Headless/IntuneManagement.Headless.psd1" Import-Module $modulePath -Force $defaultTypes = Get-DefaultIntunePolicyObjectTypes $settingsPath = Get-DefaultSettingsPath $preloadedTenantId = $null if(Test-Path $settingsPath) { try { $settings = Get-Content $settingsPath -Raw | ConvertFrom-Json if($settings.TenantId) { $preloadedTenantId = $settings.TenantId } } catch {} } #endregion while($true) { Clear-Host Write-Host "========================================" -ForegroundColor Cyan Write-Host " IntuneManagement Terminal UI" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan Write-Host " Press Esc to go back, Space to select" -ForegroundColor DarkGray # 1. Action $action = Select-MenuItem -Items @("Export","Import") -Header "Select action" if(-not $action) { continue } # 2. TenantId $tenantPrompt = "Enter Tenant ID" if($preloadedTenantId) { $tenantPrompt += " (default: $preloadedTenantId)" } $tenantId = Read-Host $tenantPrompt if([string]::IsNullOrWhiteSpace($tenantId)) { $tenantId = $preloadedTenantId } if([string]::IsNullOrWhiteSpace($tenantId)) { Write-Host "Tenant ID is required." -ForegroundColor Red; continue } # 3. Object Types Write-Host "`nObject type selection..." -ForegroundColor Cyan $typeSelection = Select-MenuItem -Items $defaultTypes -Header "Select object types to include (Space to multi-select)" -Multi if(-not $typeSelection) { continue } # 4. Path $pathPrompt = if($action -eq "Export") { "Enter export root folder path" } else { "Enter import root folder path" } $path = Read-Host $pathPrompt if([string]::IsNullOrWhiteSpace($path)) { Write-Host "Path is required." -ForegroundColor Red; return $null } # 5. Name Filter $nameFilter = Read-Host "Name filter regex (optional, e.g. '^Win-OIB-')" # 6. Name Mutation $nameSearchPattern = Read-Host "Name search regex for mutation (optional, e.g. '^Win-OIB-')" $nameReplacePattern = if(-not [string]::IsNullOrWhiteSpace($nameSearchPattern)) { $nameReplacePattern = Read-Host "Replacement string (e.g. 'Win-TEST-')" } # 7. Import-specific options $importType = $null $includeScopeTags = $false $replaceDependencyIds = $false if($action -eq "Import") { $importType = Select-MenuItem -Items @("alwaysImport","skipIfExist","replace","replace_with_assignments","update") -Header "Select import behavior" if(-not $importType) { $importType = "alwaysImport" } $includeScopeTags = Read-YesNo -Prompt "Import scope tags?" -Default $false $replaceDependencyIds = Read-YesNo -Prompt "Replace dependency IDs?" -Default $false } # 8. Common toggles $includeAssignments = Read-YesNo -Prompt "Include assignments?" -Default $false $addCompanyName = $false if($action -eq "Export") { $addCompanyName = Read-YesNo -Prompt "Add company name to folders?" -Default $false } # 9. Review Clear-Host Write-Host "Review your selection:" -ForegroundColor Green Write-Host " Action : $action" Write-Host " TenantId : $tenantId" Write-Host " Object Types : $($typeSelection -join ', ')" if($action -eq "Export") { Write-Host " Export Path : $path" Write-Host " Add Company Name : $addCompanyName" } else { Write-Host " Import Path : $path" Write-Host " Import Type : $importType" Write-Host " Include Scope Tags : $includeScopeTags" Write-Host " Replace Dep IDs : $replaceDependencyIds" } Write-Host " Name Filter : $(if($nameFilter){$nameFilter}else{'(none)'})" Write-Host " Name Search Pattern : $(if($nameSearchPattern){$nameSearchPattern}else{'(none)'})" Write-Host " Name Replace Pattern: $(if($nameReplacePattern){$nameReplacePattern}else{'(none)'})" Write-Host " Include Assignments : $includeAssignments" $confirm = Read-Host "`nProceed? [Y/n] (or type 'back' to restart)" if($confirm -eq "back") { continue } if(-not ([string]::IsNullOrWhiteSpace($confirm) -or $confirm -match "^\s*y")) { Write-Host "Cancelled." -ForegroundColor Yellow continue } # 10. Build result $result = [PSCustomObject]@{ Action = $action TenantId = $tenantId ObjectTypes = $typeSelection NameFilter = $nameFilter NameSearchPattern = $nameSearchPattern NameReplacePattern = $nameReplacePattern IncludeAssignments = $includeAssignments } if($action -eq "Export") { $result | Add-Member -NotePropertyName ExportPath -NotePropertyValue $path $result | Add-Member -NotePropertyName AddCompanyName -NotePropertyValue $addCompanyName } else { $result | Add-Member -NotePropertyName ImportPath -NotePropertyValue $path $result | Add-Member -NotePropertyName ImportType -NotePropertyValue $importType $result | Add-Member -NotePropertyName IncludeScopeTags -NotePropertyValue $includeScopeTags $result | Add-Member -NotePropertyName ReplaceDependencyIds -NotePropertyValue $replaceDependencyIds } return $result }