268 lines
8.7 KiB
PowerShell
268 lines
8.7 KiB
PowerShell
#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-FzfHint
|
|
{
|
|
if(Test-FzfAvailable) { return }
|
|
Write-Host "[fzf not found]" -ForegroundColor Yellow -NoNewline
|
|
Write-Host " Install fzf for the best interactive menu experience. Falling back to numbered menus." -ForegroundColor DarkGray
|
|
if($IsMacOS)
|
|
{
|
|
Write-Host " Install: brew install fzf" -ForegroundColor DarkGray
|
|
}
|
|
elseif($IsLinux)
|
|
{
|
|
Write-Host " Install: sudo apt install fzf (or dnf/pacman)" -ForegroundColor DarkGray
|
|
}
|
|
else
|
|
{
|
|
Write-Host " Install: winget install junegunn.fzf (or choco install fzf)" -ForegroundColor DarkGray
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
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
|
|
|
|
Show-FzfHint
|
|
|
|
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
|
|
}
|