Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e333af978c | |||
| 70679cba48 |
@@ -1,5 +1,25 @@
|
|||||||
# macOS Intune Toolkit Changelog
|
# macOS Intune Toolkit Changelog
|
||||||
|
|
||||||
|
## 2026-04-16 — v4.1.0 — Accountability, PIM & Auth Management
|
||||||
|
|
||||||
|
### Modified
|
||||||
|
- **`Scripts/Initialize-IntuneAuth.ps1`**
|
||||||
|
- App registrations are now named after the **authenticated Entra user** (e.g., `IntuneManagement-tomas.kracmar@cqre.net`) instead of the local OS username. This improves audit-log traceability when multiple admins use the toolkit against the same tenant.
|
||||||
|
- Added `-Delete` switch to remove local tenant credentials (`Settings.json` + macOS Keychain) without touching the Entra app registration.
|
||||||
|
- Added `-DeleteApp` switch to delete both the **Entra app registration** and local credentials.
|
||||||
|
- Onboarding now automatically caches the tenant display name after auth setup, so the TUI shows friendly names immediately.
|
||||||
|
- Added `Organization.Read.All` to the `Connect-MgGraph` scopes to support tenant name caching.
|
||||||
|
|
||||||
|
- **`Scripts/Start-IntuneToolkit.ps1`**
|
||||||
|
- Added menu items **14** (delete local auth) and **15** (delete auth + app registration) to the TUI.
|
||||||
|
- Selecting **"[+ Onboard new tenant]"** now runs the auth initializer immediately and restarts the launcher, instead of dropping into the main menu for an unconfigured tenant.
|
||||||
|
- The TUI now exits cleanly after deleting tenant auth.
|
||||||
|
|
||||||
|
- **`README.md`**
|
||||||
|
- Added **Accountability & PIM caveats** section explaining the trade-offs of app-only auth versus delegated auth, and how app naming affects audit logs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-04-13 — API Permissions Sync for `Initialize-IntuneAuth.ps1`
|
## 2026-04-13 — API Permissions Sync for `Initialize-IntuneAuth.ps1`
|
||||||
|
|
||||||
### Modified
|
### Modified
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Cross-platform, headless Intune policy export/import with PowerShell.
|
Cross-platform, headless Intune policy export/import with PowerShell.
|
||||||
|
|
||||||
**Current version:** `4.0.0` — see [`CHANGELOG_macOS_IntuneToolkit.md`](CHANGELOG_macOS_IntuneToolkit.md) for recent changes.
|
**Current version:** `4.1.0` — see [`CHANGELOG_macOS_IntuneToolkit.md`](CHANGELOG_macOS_IntuneToolkit.md) for recent changes.
|
||||||
|
|
||||||
This repository is now CLI-first. The old WPF application surface has been removed from the repo. The supported workflow is:
|
This repository is now CLI-first. The old WPF application surface has been removed from the repo. The supported workflow is:
|
||||||
|
|
||||||
@@ -154,3 +154,12 @@ pwsh ./Start-HeadlessIntune.ps1 `
|
|||||||
* Browser auth uses the system browser and a loopback redirect.
|
* Browser auth uses the system browser and a loopback redirect.
|
||||||
* If you omit `-AppId` with `-AuthMode Browser`, the CLI defaults to the Microsoft Graph PowerShell public client app id `14d82eec-204b-4c2f-b7e8-296a70dab67e`.
|
* If you omit `-AppId` with `-AuthMode Browser`, the CLI defaults to the Microsoft Graph PowerShell public client app id `14d82eec-204b-4c2f-b7e8-296a70dab67e`.
|
||||||
* If your own app registration does not allow loopback redirects, pass `-AppId` and `-RedirectUri "http://localhost"` and configure the same redirect URI in Entra ID.
|
* If your own app registration does not allow loopback redirects, pass `-AppId` and `-RedirectUri "http://localhost"` and configure the same redirect URI in Entra ID.
|
||||||
|
|
||||||
|
## Accountability & PIM caveats
|
||||||
|
|
||||||
|
By default `Initialize-IntuneAuth.ps1` creates an **app-only** registration. Every Graph call is authenticated as the service principal, not as an individual user.
|
||||||
|
|
||||||
|
* **Audit logs** show the app's display name (e.g., `IntuneManagement-tomas.kracmar@cqre.net`), not the admin's UPN. The initializer now automatically names the app after the **authenticated Entra user** to improve traceability.
|
||||||
|
* **PIM is not enforced** for app-only secrets. The service principal has standing permissions, so write operations can occur outside an elevated PIM window.
|
||||||
|
* If you need strict PIM compliance, use **delegated authentication** (`-AuthMode Browser` or `-AuthMode DeviceCode`) so calls are made in the signed-in user's context. Note that `DeviceCode` may be blocked by Conditional Access policies.
|
||||||
|
* To fully remove a tenant's local credentials **and** the Entra app registration, use menu item **15** in the TUI or run `./Scripts/Initialize-IntuneAuth.ps1 -TenantId "<id>" -DeleteApp`.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ The Microsoft Entra tenant ID (GUID). If omitted, the script reads from
|
|||||||
existing settings or prompts interactively.
|
existing settings or prompts interactively.
|
||||||
|
|
||||||
.PARAMETER DisplayName
|
.PARAMETER DisplayName
|
||||||
The display name for the app registration. Default: IntuneManagement-Headless.
|
The display name for the app registration. Default: IntuneManagement-<current user name>.
|
||||||
|
|
||||||
.PARAMETER SettingsFile
|
.PARAMETER SettingsFile
|
||||||
Path to the JSON settings file. If omitted, defaults to the macOS_IntuneManagement
|
Path to the JSON settings file. If omitted, defaults to the macOS_IntuneManagement
|
||||||
@@ -25,16 +25,28 @@ settings folder (~/Library/Application Support/macOS_IntuneManagement/Settings.j
|
|||||||
|
|
||||||
.PARAMETER Force
|
.PARAMETER Force
|
||||||
Recreate the app registration and secret even if existing credentials are found.
|
Recreate the app registration and secret even if existing credentials are found.
|
||||||
|
|
||||||
|
.PARAMETER Delete
|
||||||
|
Remove the saved tenant credentials from the local settings file (and macOS Keychain if applicable).
|
||||||
|
Does not delete the app registration in Entra ID.
|
||||||
|
|
||||||
|
.PARAMETER DeleteApp
|
||||||
|
Remove the app registration from the Entra tenant and clean up local credentials.
|
||||||
|
Requires the same Microsoft Graph permissions as initialization.
|
||||||
#>
|
#>
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[string]$TenantId,
|
[string]$TenantId,
|
||||||
|
|
||||||
[string]$DisplayName = "IntuneManagement-Headless",
|
[string]$DisplayName = "IntuneManagement-$([Environment]::UserName)",
|
||||||
|
|
||||||
[string]$SettingsFile,
|
[string]$SettingsFile,
|
||||||
|
|
||||||
[switch]$Force
|
[switch]$Force,
|
||||||
|
|
||||||
|
[switch]$Delete,
|
||||||
|
|
||||||
|
[switch]$DeleteApp
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
@@ -67,6 +79,45 @@ function Get-AuthSetting
|
|||||||
param($Key, [string]$SubPath = "", $DefaultValue = $null)
|
param($Key, [string]$SubPath = "", $DefaultValue = $null)
|
||||||
Get-Setting -SubPath $SubPath -Key $Key -DefaultValue $DefaultValue
|
Get-Setting -SubPath $SubPath -Key $Key -DefaultValue $DefaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Remove-LocalAuthSettings
|
||||||
|
{
|
||||||
|
param([string]$TenantId, [string]$AppId)
|
||||||
|
|
||||||
|
if ($global:JsonSettingsObj)
|
||||||
|
{
|
||||||
|
if ($global:JsonSettingsObj.ContainsKey($TenantId))
|
||||||
|
{
|
||||||
|
$global:JsonSettingsObj.Remove($TenantId) | Out-Null
|
||||||
|
Write-Host "Removed tenant settings for $TenantId from $SettingsFile" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($global:JsonSettingsObj["TenantId"] -eq $TenantId)
|
||||||
|
{
|
||||||
|
$global:JsonSettingsObj.Remove("TenantId") | Out-Null
|
||||||
|
Write-Host "Removed default TenantId from $SettingsFile" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
$global:JsonSettingsObj | ConvertTo-Json -Depth 30 | Out-File -LiteralPath $global:JSonSettingFile -Force -Encoding utf8
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($AppId)
|
||||||
|
{
|
||||||
|
if ($IsMacOS)
|
||||||
|
{
|
||||||
|
$null = security delete-generic-password -a "IntuneManagement" -s "IntuneMgmt-$AppId" 2>$null
|
||||||
|
Write-Host "Removed client secret for AppId $AppId from macOS Keychain" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write-Host "Client secret was stored in $SettingsFile and has been removed along with the tenant node." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write-Warning "No saved credentials found for tenant $TenantId."
|
||||||
|
}
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Determine TenantId
|
#region Determine TenantId
|
||||||
@@ -85,6 +136,96 @@ if (-not $TenantId)
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Delete saved credentials
|
||||||
|
if ($Delete)
|
||||||
|
{
|
||||||
|
$appIdToClean = Get-AuthSetting -SubPath $TenantId -Key "GraphAzureAppId"
|
||||||
|
Remove-LocalAuthSettings -TenantId $TenantId -AppId $appIdToClean
|
||||||
|
return
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Microsoft Graph modules
|
||||||
|
$requiredModules = @("Microsoft.Graph.Authentication", "Microsoft.Graph.Applications")
|
||||||
|
foreach ($mod in $requiredModules)
|
||||||
|
{
|
||||||
|
if (-not (Get-Module $mod -ListAvailable))
|
||||||
|
{
|
||||||
|
throw "Module '$mod' is not installed. Run: Install-Module Microsoft.Graph -Scope CurrentUser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Import-Module Microsoft.Graph.Authentication -Force
|
||||||
|
Import-Module Microsoft.Graph.Applications -Force
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Connect to Graph
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Cyan
|
||||||
|
Write-Host "A browser window will open for authentication." -ForegroundColor Cyan
|
||||||
|
Connect-MgGraph -Scopes "Application.ReadWrite.All", "AppRoleAssignment.ReadWrite.All", "Organization.Read.All" -NoWelcome
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Resolve authenticated user for app naming
|
||||||
|
if (-not $PSBoundParameters.ContainsKey('DisplayName'))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$ctx = Get-MgContext -ErrorAction Stop
|
||||||
|
if ($ctx -and $ctx.Account)
|
||||||
|
{
|
||||||
|
$DisplayName = "IntuneManagement-$($ctx.Account)"
|
||||||
|
Write-Host "Using app display name: $DisplayName" -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Cache tenant name
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$org = Get-MgOrganization -ErrorAction Stop
|
||||||
|
if ($org -and $org.DisplayName)
|
||||||
|
{
|
||||||
|
Save-AuthSetting -SubPath $TenantId -Key "TenantName" -Value $org.DisplayName
|
||||||
|
Write-Host "Cached tenant name: $($org.DisplayName)" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Write-Warning "Failed to cache tenant name: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Delete app registration and local credentials
|
||||||
|
if ($DeleteApp)
|
||||||
|
{
|
||||||
|
$appIdToClean = Get-AuthSetting -SubPath $TenantId -Key "GraphAzureAppId"
|
||||||
|
if ($appIdToClean)
|
||||||
|
{
|
||||||
|
$appToDelete = Get-MgApplication -Filter "appId eq '$appIdToClean'" -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
if ($appToDelete)
|
||||||
|
{
|
||||||
|
Remove-MgApplication -ApplicationId $appToDelete.Id
|
||||||
|
Write-Host "Deleted app registration $($appToDelete.DisplayName) ($appIdToClean) from tenant $TenantId" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write-Warning "App registration $appIdToClean not found in tenant $TenantId."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Write-Warning "No AppId found in local settings for tenant $TenantId."
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-LocalAuthSettings -TenantId $TenantId -AppId $appIdToClean
|
||||||
|
Disconnect-MgGraph | Out-Null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Check for existing credentials
|
#region Check for existing credentials
|
||||||
$existingAppId = Get-AuthSetting -SubPath $TenantId -Key "GraphAzureAppId"
|
$existingAppId = Get-AuthSetting -SubPath $TenantId -Key "GraphAzureAppId"
|
||||||
if ($existingAppId -and -not $Force)
|
if ($existingAppId -and -not $Force)
|
||||||
@@ -116,27 +257,6 @@ if ($existingAppId -and -not $Force)
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Microsoft Graph modules
|
|
||||||
$requiredModules = @("Microsoft.Graph.Authentication", "Microsoft.Graph.Applications")
|
|
||||||
foreach ($mod in $requiredModules)
|
|
||||||
{
|
|
||||||
if (-not (Get-Module $mod -ListAvailable))
|
|
||||||
{
|
|
||||||
throw "Module '$mod' is not installed. Run: Install-Module Microsoft.Graph -Scope CurrentUser"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Import-Module Microsoft.Graph.Authentication -Force
|
|
||||||
Import-Module Microsoft.Graph.Applications -Force
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Connect to Graph
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Cyan
|
|
||||||
Write-Host "A browser window will open for authentication." -ForegroundColor Cyan
|
|
||||||
Connect-MgGraph -Scopes "Application.ReadWrite.All", "AppRoleAssignment.ReadWrite.All" -NoWelcome
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region App registration
|
#region App registration
|
||||||
$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
|
$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
|
||||||
if (-not $graphSp)
|
if (-not $graphSp)
|
||||||
|
|||||||
@@ -261,6 +261,12 @@ if(-not $TenantId)
|
|||||||
Write-Host "No tenant ID provided. Exiting." -ForegroundColor Yellow
|
Write-Host "No tenant ID provided. Exiting." -ForegroundColor Yellow
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
$initPath = Join-Path $projectRoot "Scripts/Initialize-IntuneAuth.ps1"
|
||||||
|
& $initPath -TenantId $TenantId
|
||||||
|
Write-Host "`nOnboarding complete. Restarting launcher..." -ForegroundColor Green
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
& $PSCommandPath
|
||||||
|
exit 0
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -300,6 +306,8 @@ $commonParams = @{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$menuItems = @(
|
$menuItems = @(
|
||||||
|
"15. Delete tenant auth and app registration"
|
||||||
|
"14. Delete local tenant auth only"
|
||||||
"13. Refresh tenant names"
|
"13. Refresh tenant names"
|
||||||
"12. Initialize auth (one-time setup)"
|
"12. Initialize auth (one-time setup)"
|
||||||
"11. Deploy baseline (dry-run / WhatIf)"
|
"11. Deploy baseline (dry-run / WhatIf)"
|
||||||
@@ -360,6 +368,8 @@ while($true)
|
|||||||
11 { $script = "Scripts/Deploy-IntuneBaseline.ps1"; $commonParams.WhatIf = $true }
|
11 { $script = "Scripts/Deploy-IntuneBaseline.ps1"; $commonParams.WhatIf = $true }
|
||||||
12 { $script = "Scripts/Initialize-IntuneAuth.ps1" }
|
12 { $script = "Scripts/Initialize-IntuneAuth.ps1" }
|
||||||
13 { $script = $null }
|
13 { $script = $null }
|
||||||
|
14 { $script = "Scripts/Initialize-IntuneAuth.ps1" }
|
||||||
|
15 { $script = "Scripts/Initialize-IntuneAuth.ps1" }
|
||||||
default { continue }
|
default { continue }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,9 +434,25 @@ while($true)
|
|||||||
@("AppId","Secret","Certificate","AuthMode","RedirectUri","Interactive","Mode","WhatIf") | ForEach-Object { $launchParams.Remove($_) }
|
@("AppId","Secret","Certificate","AuthMode","RedirectUri","Interactive","Mode","WhatIf") | ForEach-Object { $launchParams.Remove($_) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($choiceNumber -eq 14)
|
||||||
|
{
|
||||||
|
$launchParams.Delete = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
if($choiceNumber -eq 15)
|
||||||
|
{
|
||||||
|
$launchParams.DeleteApp = $true
|
||||||
|
}
|
||||||
|
|
||||||
# Execute in same process so TUI flows naturally
|
# Execute in same process so TUI flows naturally
|
||||||
& $scriptPath @launchParams
|
& $scriptPath @launchParams
|
||||||
|
|
||||||
|
if($choiceNumber -eq 14 -or $choiceNumber -eq 15)
|
||||||
|
{
|
||||||
|
Write-Host "`nTenant auth deleted. Exiting." -ForegroundColor Yellow
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
Write-Host "`nPress any key to return to the menu..." -ForegroundColor DarkGray
|
Write-Host "`nPress any key to return to the menu..." -ForegroundColor DarkGray
|
||||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user