Files
tomas.kracmar d3e0769799 release: v4.1.0 — restructure entry points, add CIS baselines, reporting tools and fzf hints
- Restructure launchers: Start-IntuneToolkit.ps1 moves to repo root;
  Start-HeadlessIntune.ps1 moves to Scripts/; TUI helper moves to Scripts/Private/
- Add AGENTS.md with project architecture, entry points, and security notes
- Add CIS M365 baseline assets (CISM365-v7, M365-CIS-Rapid) and reporting scripts
- Add Python reporting utilities (Export-SettingsReport, Export-AssignmentReport,
  Export-ObjectInventoryReport) and CA wizard helpers
- Update Deploy-IntuneBaseline.ps1 with Merge conflict resolution, ReportPath,
  and optimized group loading
- Update Initialize-IntuneAuth.ps1 with -RotateSecret and configurable secret expiry
- Update Extensions for Settings Catalog definition auto-export
- Update README with v4.1.0, new entry points and script catalog
- Bump VERSION to 4.1.0
- Harden .gitignore against .DS_Store, __pycache__, .venv-pdf/, local exports,
  Settings.json and IntuneManagement.log
2026-06-14 15:24:42 +02:00

166 lines
5.9 KiB
PowerShell

#requires -Version 7.0
<#
.SYNOPSIS
Deploy an Intune or CIS M365 baseline to multiple tenants from a CSV manifest.
.DESCRIPTION
Reads a CSV file with one row per tenant, invokes Deploy-IntuneBaseline.ps1 or
Deploy-CISM365Baseline.ps1 for each row, and aggregates all per-tenant reports
into a single combined CSV summary.
CSV columns (Deploy-IntuneBaseline mode):
TenantId, BaselinePath, AppId, Secret, Certificate, AuthMode, ConflictResolution, WhatIf
CSV columns (Deploy-CISM365Baseline mode):
TenantId, BaselinePath, AppId, Secret, Certificate, AuthMode, Mode, Workloads, WhatIf
All columns except TenantId and BaselinePath are optional.
.PARAMETER CsvPath
Path to the CSV manifest file.
.PARAMETER ScriptMode
Which deployment script to invoke per tenant: 'Intune' or 'CIS'. Default: Intune.
.PARAMETER OutputDir
Directory for per-tenant reports and the combined summary. Default: same directory as CsvPath.
.PARAMETER WhatIf
Propagates WhatIf to every tenant run, overriding the CSV column.
.EXAMPLE
./Scripts/Invoke-BaselineBatch.ps1 -CsvPath ./tenants.csv -ScriptMode Intune
.EXAMPLE
./Scripts/Invoke-BaselineBatch.ps1 -CsvPath ./tenants.csv -ScriptMode CIS -WhatIf
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$CsvPath,
[ValidateSet("Intune","CIS")]
[string]$ScriptMode = "Intune",
[string]$OutputDir,
[switch]$WhatIf
)
$ErrorActionPreference = "Stop"
$csvResolved = Resolve-Path $CsvPath | Select-Object -ExpandProperty Path
if (-not (Test-Path $csvResolved)) { throw "CSV not found: $CsvPath" }
$rows = Import-Csv -Path $csvResolved
if (-not $rows -or $rows.Count -eq 0) { throw "CSV is empty: $CsvPath" }
$scriptDir = Split-Path -Parent $PSScriptRoot
$intuneScript = Join-Path $scriptDir "Scripts/Deploy-IntuneBaseline.ps1"
$cisScript = Join-Path $scriptDir "Scripts/Deploy-CISM365Baseline.ps1"
$targetScript = if ($ScriptMode -eq "CIS") { $cisScript } else { $intuneScript }
if (-not (Test-Path $targetScript)) { throw "Deployment script not found: $targetScript" }
$resolvedOutputDir = if ($OutputDir) { $OutputDir } else { Split-Path -Parent $csvResolved }
if (-not (Test-Path $resolvedOutputDir)) { New-Item -ItemType Directory -Path $resolvedOutputDir | Out-Null }
$ts = Get-Date -Format 'yyyyMMdd_HHmmss'
$batchSummary = [System.Collections.Generic.List[PSCustomObject]]::new()
$rowIndex = 0
foreach ($row in $rows)
{
$rowIndex++
$tenantId = $row.TenantId?.Trim()
$baselinePath = $row.BaselinePath?.Trim()
if ([string]::IsNullOrWhiteSpace($tenantId) -or [string]::IsNullOrWhiteSpace($baselinePath))
{
Write-Warning "Row $rowIndex skipped: TenantId or BaselinePath is empty."
$batchSummary.Add([PSCustomObject]@{
Row = $rowIndex
TenantId = $tenantId
Baseline = $baselinePath
Outcome = 'Skipped-InvalidRow'
ReportPath = $null
Error = 'TenantId or BaselinePath empty'
})
continue
}
$tenantReportPath = Join-Path $resolvedOutputDir "${tenantId}_${ts}.csv"
Write-Host ""
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host "Tenant $rowIndex/$($rows.Count): $tenantId" -ForegroundColor Cyan
Write-Host "Baseline : $baselinePath" -ForegroundColor Cyan
Write-Host "======================================================" -ForegroundColor Cyan
$params = @{
TenantId = $tenantId
BaselinePath = $baselinePath
}
if ($row.PSObject.Properties['AppId'] -and $row.AppId) { $params.AppId = $row.AppId }
if ($row.PSObject.Properties['Secret'] -and $row.Secret) { $params.Secret = $row.Secret }
if ($row.PSObject.Properties['Certificate'] -and $row.Certificate) { $params.Certificate = $row.Certificate }
if ($row.PSObject.Properties['AuthMode'] -and $row.AuthMode) { $params.AuthMode = $row.AuthMode }
if ($WhatIf -or ($row.PSObject.Properties['WhatIf'] -and $row.WhatIf -match '(?i)^true|yes|1$'))
{
$params.WhatIf = $true
}
if ($ScriptMode -eq "Intune")
{
if ($row.PSObject.Properties['ConflictResolution'] -and $row.ConflictResolution) { $params.ConflictResolution = $row.ConflictResolution }
$params.ReportPath = $tenantReportPath
}
else
{
if ($row.PSObject.Properties['Mode'] -and $row.Mode) { $params.Mode = $row.Mode }
if ($row.PSObject.Properties['Workloads'] -and $row.Workloads)
{
$params.Workloads = $row.Workloads -split '\s*[,;]\s*'
}
}
$outcome = 'Success'
$errorMsg = $null
try
{
& $targetScript @params
}
catch
{
$outcome = 'Failed'
$errorMsg = $_.Exception.Message
Write-Warning "Tenant $tenantId failed: $errorMsg"
}
$batchSummary.Add([PSCustomObject]@{
Row = $rowIndex
TenantId = $tenantId
Baseline = $baselinePath
Outcome = $outcome
ReportPath = if ($ScriptMode -eq "Intune" -and (Test-Path $tenantReportPath)) { $tenantReportPath } else { $null }
Error = $errorMsg
})
}
$summaryPath = Join-Path $resolvedOutputDir "BatchSummary_${ts}.csv"
$batchSummary | Export-Csv -Path $summaryPath -NoTypeInformation -Force
Write-Host ""
Write-Host "======================================================" -ForegroundColor Green
Write-Host "Batch complete. $($rows.Count) tenant(s) processed." -ForegroundColor Green
Write-Host "Summary: $summaryPath" -ForegroundColor Green
$failed = $batchSummary | Where-Object { $_.Outcome -ne 'Success' }
if ($failed)
{
Write-Host "Failed tenants:" -ForegroundColor Red
$failed | ForEach-Object { Write-Host " $($_.TenantId): $($_.Error)" -ForegroundColor Red }
}
Write-Host "======================================================" -ForegroundColor Green