v0.1.28 — Umbenennen beim Import, UI-Fixes, Geraete-Export repariert
Build & Release MSI / build-msi (push) Canceled after 0s
Build & Release MSI / build-msi (push) Canceled after 0s
Neu: - Umbenennen beim Import: Policy-Name vor dem Anlegen anpassbar (Dialog "Policies importieren"), richtiges Namensfeld je Typ Behoben: - Geraete-Export im App Report: entfernter sync-Endpoint getDeviceInstallStatusReport (400) -> async Export-Job DeviceInstallStatusByApp; ZIP/CSV-Logik in Get-IntuneReportRows - Darkmode-Kontrast im Zuweisungs-Dialog (Theme-Variablen statt fester Farben) - Dialog-Breite: lange Policy-Namen brechen um statt H-Scroll Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+69
-48
@@ -1995,8 +1995,49 @@ function Get-AppInstallReportEndpoint {
|
||||
return @{ items = $items; count = $items.Count }
|
||||
}
|
||||
|
||||
# Intune-Report via asynchronem Export-Job (exportJobs) -> CSV-Zeilen.
|
||||
# Kein 'select' (unbekannte Spalten wuerden 400 ausloesen) — der Aufrufer
|
||||
# greift tolerant auf die tatsaechlich gelieferten Spalten zu.
|
||||
function Get-IntuneReportRows {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$ReportName,
|
||||
[string]$Filter = '',
|
||||
[int]$TimeoutSec = 120
|
||||
)
|
||||
$jobBody = @{ reportName = $ReportName; filter = $Filter } | ConvertTo-Json -Compress
|
||||
$job = Invoke-MgGraphRequestRetry `
|
||||
-Uri 'https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs' `
|
||||
-Method POST -Body $jobBody -ContentType 'application/json'
|
||||
$jobId = $job.id
|
||||
$status = $job.status
|
||||
$waited = 0
|
||||
while ($status -ne 'completed' -and $status -ne 'failed' -and $waited -lt $TimeoutSec) {
|
||||
Start-Sleep -Seconds 3
|
||||
$waited += 3
|
||||
$job = Invoke-MgGraphRequestRetry -Uri "https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs/$jobId"
|
||||
$status = $job.status
|
||||
}
|
||||
if ($status -ne 'completed') { throw "Export-Job '$ReportName' nicht abgeschlossen (Status: $status nach $waited s)" }
|
||||
|
||||
$tmpZip = [System.IO.Path]::GetTempFileName() + '.zip'
|
||||
$tmpDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "IntuneReport_$jobId")
|
||||
try {
|
||||
Invoke-WebRequest -Uri $job.url -OutFile $tmpZip -UseBasicParsing
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($tmpZip, $tmpDir)
|
||||
$csv = Get-ChildItem -Path $tmpDir -Filter '*.csv' | Select-Object -First 1
|
||||
if (-not $csv) { throw "Keine CSV im Export-ZIP gefunden" }
|
||||
return @(Import-Csv -Path $csv.FullName -Encoding UTF8)
|
||||
} finally {
|
||||
Remove-Item -Path $tmpZip -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -Path $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AppDeviceStatusEndpoint {
|
||||
# Verwendet getDeviceInstallStatusReport (synchron, paginiert) statt deviceStatuses.
|
||||
# Per-Device-Installationsstatus einer App via Export-Job 'DeviceInstallStatusByApp'.
|
||||
# (Der frueher genutzte synchrone Endpoint getDeviceInstallStatusReport existiert
|
||||
# nicht mehr -> 400 "Resource not found for the segment".)
|
||||
param([hashtable]$Query)
|
||||
$err = Test-Connected
|
||||
if ($err) { return $err }
|
||||
@@ -2006,58 +2047,38 @@ function Get-AppDeviceStatusEndpoint {
|
||||
return @{ __status = 400; error = "appId fehlt" }
|
||||
}
|
||||
|
||||
Write-Host "[DEVSTATUS] Lade Install-Status fuer App $appId via Reports-API..." -ForegroundColor DarkCyan
|
||||
$pageSize = 50
|
||||
$skip = 0
|
||||
$allRows = @()
|
||||
$cols = $null
|
||||
|
||||
do {
|
||||
$bodyJson = "{""filter"":""(ApplicationId eq '$appId')"",""select"":[],""skip"":$skip,""top"":$pageSize,""orderBy"":[]}"
|
||||
try {
|
||||
$resp = Invoke-MgGraphRequestRetry `
|
||||
-Uri 'https://graph.microsoft.com/beta/deviceManagement/reports/getDeviceInstallStatusReport' `
|
||||
-Method POST -Body $bodyJson -ContentType 'application/json'
|
||||
} catch {
|
||||
return @{ __status = 500; error = "Graph-Fehler: $($_.Exception.Message)" }
|
||||
}
|
||||
|
||||
if (-not $cols) {
|
||||
$cols = @($resp.Schema | ForEach-Object { $_.Column })
|
||||
Write-Host " [DEVSTATUS] Spalten: $($cols -join ',')" -ForegroundColor DarkGray
|
||||
}
|
||||
$rows = @($resp.Values)
|
||||
$allRows += $rows
|
||||
$skip += $pageSize
|
||||
} while ($rows.Count -eq $pageSize)
|
||||
|
||||
Write-Host " -> $($allRows.Count) Eintraege" -ForegroundColor DarkGray
|
||||
|
||||
if (-not $cols -or $allRows.Count -eq 0) {
|
||||
return @{ items = @(); count = 0 }
|
||||
Write-Host "[DEVSTATUS] Export-Job (DeviceInstallStatusByApp) fuer App $appId..." -ForegroundColor DarkCyan
|
||||
try {
|
||||
$rows = @(Get-IntuneReportRows -ReportName 'DeviceInstallStatusByApp' -Filter "(ApplicationId eq '$appId')")
|
||||
} catch {
|
||||
$msg = $_.Exception.Message
|
||||
try { if ($_.ErrorDetails.Message) { $msg = $_.ErrorDetails.Message } } catch {}
|
||||
return @{ __status = 500; error = "Graph-Fehler: $msg" }
|
||||
}
|
||||
Write-Host " -> $($rows.Count) Eintraege" -ForegroundColor DarkGray
|
||||
if ($rows.Count -eq 0) { return @{ items = @(); count = 0 } }
|
||||
|
||||
function ColIdx($name) { [Array]::IndexOf($cols, $name) }
|
||||
$iDevice = ColIdx 'DeviceName'
|
||||
$iUser = ColIdx 'UserName'
|
||||
$iState = ColIdx 'InstallState'
|
||||
$iDetail = ColIdx 'InstallStateDetail'
|
||||
$iErr = ColIdx 'ErrorCode'
|
||||
$iOs = ColIdx 'OSVersion'
|
||||
$iSync = ColIdx 'LastModifiedDateTime'
|
||||
if ($iOs -lt 0) { $iOs = ColIdx 'OsVersion' }
|
||||
if ($iSync -lt 0) { $iSync = ColIdx 'LastSyncDateTime' }
|
||||
Write-Host " [DEVSTATUS] Spalten: $(($rows[0].PSObject.Properties.Name) -join ',')" -ForegroundColor DarkGray
|
||||
|
||||
$result = @($allRows | ForEach-Object {
|
||||
# Spaltennamen des Reports koennen variieren -> tolerant mit Fallbacks lesen.
|
||||
$col = {
|
||||
param($row, [string[]]$names)
|
||||
foreach ($n in $names) {
|
||||
$p = $row.PSObject.Properties[$n]
|
||||
if ($p -and $null -ne $p.Value -and [string]$p.Value -ne '') { return [string]$p.Value }
|
||||
}
|
||||
return ''
|
||||
}
|
||||
$result = @($rows | ForEach-Object {
|
||||
$r = $_
|
||||
[pscustomobject]@{
|
||||
DeviceName = if ($iDevice -ge 0) { [string]$r[$iDevice] } else { '' }
|
||||
UserName = if ($iUser -ge 0) { [string]$r[$iUser] } else { '' }
|
||||
InstallState = if ($iState -ge 0) { [string]$r[$iState] } else { '' }
|
||||
InstallStateDetail = if ($iDetail -ge 0) { [string]$r[$iDetail] } else { '' }
|
||||
ErrorCode = if ($iErr -ge 0) { [string]$r[$iErr] } else { '' }
|
||||
OsVersion = if ($iOs -ge 0) { [string]$r[$iOs] } else { '' }
|
||||
LastSyncDateTime = if ($iSync -ge 0) { [string]$r[$iSync] } else { '' }
|
||||
DeviceName = & $col $r @('DeviceName')
|
||||
UserName = & $col $r @('UserName','UserPrincipalName')
|
||||
InstallState = & $col $r @('InstallState_loc','InstallState','AppInstallState_loc','AppInstallState')
|
||||
InstallStateDetail = & $col $r @('InstallStateDetail_loc','InstallStateDetail','AppInstallStateDetail_loc','AppInstallStateDetail')
|
||||
ErrorCode = & $col $r @('ErrorCode','HexErrorCode')
|
||||
OsVersion = & $col $r @('OSVersion','OsVersion','Platform')
|
||||
LastSyncDateTime = & $col $r @('LastModifiedDateTime','LastSyncDateTime')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -119,6 +119,15 @@ function Get-PolicyProp {
|
||||
return $null
|
||||
}
|
||||
|
||||
# Property setzen — Hashtable ODER PSCustomObject (fuer Import-Umbenennung).
|
||||
function Set-PolicyProp {
|
||||
param($Obj, [string]$Name, $Value)
|
||||
if ($null -eq $Obj) { return }
|
||||
if ($Obj -is [System.Collections.IDictionary]) { $Obj[$Name] = $Value; return }
|
||||
if ($Obj.PSObject.Properties[$Name]) { $Obj.$Name = $Value }
|
||||
else { $Obj | Add-Member -NotePropertyName $Name -NotePropertyValue $Value -Force }
|
||||
}
|
||||
|
||||
# Grobe Plattform-Bezeichnung fuer die Listenanzeige.
|
||||
function Get-PolicyPlatformLabel {
|
||||
param($Raw, [string]$Type)
|
||||
@@ -563,6 +572,14 @@ function Import-PoliciesEndpoint {
|
||||
$results += @{ policyName = [string]$policyName; success = $false; error = 'Envelope ohne "policy"-Feld' }
|
||||
continue
|
||||
}
|
||||
# Optionale Umbenennung: das richtige Namensfeld je Typ setzen.
|
||||
$newName = [string](Get-PolicyProp $env 'newName')
|
||||
if ($newName -and $newName.Trim()) {
|
||||
$cfg = Get-PolicyTypeConfigByExportType $exportType
|
||||
$nameField = if ($cfg) { $cfg.NameField } else { 'displayName' }
|
||||
Set-PolicyProp $policy $nameField $newName.Trim()
|
||||
$policyName = $newName.Trim()
|
||||
}
|
||||
try {
|
||||
$imported = Import-GraphPolicyByExportType -ExportType $exportType -Policy $policy
|
||||
$results += @{ policyName = [string]$policyName; exportType = $exportType; success = $true; newId = [string](Get-PolicyProp $imported 'id') }
|
||||
|
||||
Reference in New Issue
Block a user