Policy-Import haerten, Admin-Vorlagen, Zuweisung nach Import, Git-Snapshot
Neu: - Administrative Vorlagen (ADMX / groupPolicyConfigurations) als vierter Policy-Typ fuer Export/Import (GET /api/policies/administrativetemplate) - Zuweisung direkt nach Import: pro Policy Include-/Exclude-Gruppen (POST /api/policies/assign, typ-spezifische /assign-Action) - Git-Snapshot: versioniertes Policy-Backup in lokalen Git-Ordner (Settings-Sektion policyBackup) - Import akzeptiert Git-Snapshot-Dateien (policyType neben exportType) Behoben: - Settings-Catalog-Import schema-konform: Collection-Properties immer als Array (repariert PS-5.1-Roundtrip), null -> [], Wrapper-@odata.type, read-only id entfernt - Import-Phantom-Fehler (@($null)-Geisterdurchlauf im Archiv-Pfad) - Frontend-Cache-Reset beim Tenant-Wechsel (resetClientState) - WAM-Anmeldefenster zuverlaessig im Vordergrund (AttachThreadInput) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+75
-3
@@ -53,9 +53,13 @@ function Invoke-ApiHandler {
|
||||
"GET /api/policies/compliance" { return Get-CompliancePoliciesEndpoint }
|
||||
"GET /api/policies/configuration" { return Get-ConfigurationProfilesEndpoint }
|
||||
"GET /api/policies/settingscatalog" { return Get-SettingsCatalogPoliciesEndpoint }
|
||||
"GET /api/policies/administrativetemplate" { return Get-AdministrativeTemplatesEndpoint }
|
||||
"POST /api/policies/export" { return Export-PoliciesEndpoint -Body $Body }
|
||||
"GET /api/policies/exports" { return Get-PolicyExportsEndpoint }
|
||||
"POST /api/policies/import" { return Import-PoliciesEndpoint -Body $Body }
|
||||
"POST /api/policies/assign" { return Invoke-PolicyAssignEndpoint -Body $Body }
|
||||
"POST /api/policies/git-snapshot" { return Invoke-PolicyGitSnapshotEndpoint }
|
||||
"POST /api/pickfolder" { return Invoke-FolderPickerEndpoint -Body $Body }
|
||||
}
|
||||
|
||||
# 2-segment fallbacks (z.B. /api/groups/<id>/members)
|
||||
@@ -928,7 +932,30 @@ public class WinFocusHelper2 {
|
||||
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc lpEnumFunc, IntPtr lParam);
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
|
||||
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
||||
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
|
||||
[DllImport("user32.dll")] public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
||||
[DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();
|
||||
[DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
|
||||
public delegate bool EnumProc(IntPtr hWnd, IntPtr lParam);
|
||||
// Holt ein Fenster zuverlaessig in den Vordergrund und umgeht den Windows-
|
||||
// Foreground-Lock: kurzer ALT-Tap (entsperrt SetForegroundWindow) + Anhaengen
|
||||
// an den Input-Thread des aktuellen Vordergrundfensters (AttachThreadInput).
|
||||
public static void ForceForeground(IntPtr hWnd) {
|
||||
keybd_event(0x12, 0, 0, UIntPtr.Zero); // ALT down -> Foreground-Lock loesen
|
||||
keybd_event(0x12, 0, 2, UIntPtr.Zero); // ALT up (KEYEVENTF_KEYUP = 2)
|
||||
IntPtr fore = GetForegroundWindow();
|
||||
uint foreThread = GetWindowThreadProcessId(fore, IntPtr.Zero);
|
||||
uint thisThread = GetCurrentThreadId();
|
||||
bool attached = false;
|
||||
if (foreThread != 0 && foreThread != thisThread) {
|
||||
attached = AttachThreadInput(foreThread, thisThread, true);
|
||||
}
|
||||
ShowWindow(hWnd, 9); // SW_RESTORE
|
||||
BringWindowToTop(hWnd);
|
||||
SetForegroundWindow(hWnd);
|
||||
if (attached) { AttachThreadInput(foreThread, thisThread, false); }
|
||||
}
|
||||
}
|
||||
'@ -ErrorAction SilentlyContinue
|
||||
$deadline = (Get-Date).AddSeconds(30)
|
||||
@@ -950,9 +977,7 @@ public class WinFocusHelper2 {
|
||||
return $true
|
||||
}, [IntPtr]::Zero) | Out-Null
|
||||
if ($script:found -ne [IntPtr]::Zero) {
|
||||
[WinFocusHelper2]::ShowWindow($script:found, 9) | Out-Null # SW_RESTORE
|
||||
[WinFocusHelper2]::BringWindowToTop($script:found) | Out-Null
|
||||
[WinFocusHelper2]::SetForegroundWindow($script:found) | Out-Null
|
||||
[WinFocusHelper2]::ForceForeground($script:found)
|
||||
Start-Sleep -Milliseconds 800
|
||||
}
|
||||
Start-Sleep -Milliseconds 400
|
||||
@@ -2597,6 +2622,53 @@ function Invoke-FilePickerEndpoint {
|
||||
return @{ ok = $true; path = $path }
|
||||
}
|
||||
|
||||
function Show-OpenFolderDialog {
|
||||
# Wie Show-OpenFileDialog, aber FolderBrowserDialog in einem STA-Prozess.
|
||||
param([string]$Title = "Ordner auswaehlen")
|
||||
$tEsc = $Title -replace "'", "''"
|
||||
$inner = @"
|
||||
`$ProgressPreference = 'SilentlyContinue'
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
`$dlg = New-Object System.Windows.Forms.FolderBrowserDialog
|
||||
`$dlg.Description = '$tEsc'
|
||||
`$owner = New-Object System.Windows.Forms.Form
|
||||
`$owner.TopMost = `$true
|
||||
`$owner.ShowInTaskbar = `$false
|
||||
`$owner.WindowState = 'Minimized'
|
||||
`$owner.Show(); `$owner.Activate()
|
||||
`$r = `$dlg.ShowDialog(`$owner)
|
||||
`$owner.Dispose()
|
||||
if (`$r -eq [System.Windows.Forms.DialogResult]::OK) { [Console]::Out.Write(`$dlg.SelectedPath) }
|
||||
"@
|
||||
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($inner))
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = "powershell.exe"
|
||||
$psi.Arguments = "-NoProfile -STA -ExecutionPolicy Bypass -EncodedCommand $encoded"
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.CreateNoWindow = $true
|
||||
try {
|
||||
$proc = [System.Diagnostics.Process]::Start($psi)
|
||||
$path = $proc.StandardOutput.ReadToEnd()
|
||||
$proc.WaitForExit()
|
||||
} catch {
|
||||
throw "Ordner-Dialog-Prozess konnte nicht gestartet werden: $($_.Exception.Message)"
|
||||
}
|
||||
return ([string]$path).Trim()
|
||||
}
|
||||
|
||||
function Invoke-FolderPickerEndpoint {
|
||||
param($Body)
|
||||
$title = if ($Body -and $Body.title) { [string]$Body.title } else { "Git-Repo-Ordner auswaehlen" }
|
||||
try {
|
||||
$path = Show-OpenFolderDialog -Title $title
|
||||
} catch {
|
||||
return @{ __status = 500; error = "Ordner-Dialog fehlgeschlagen: $($_.Exception.Message)" }
|
||||
}
|
||||
if (-not $path) { return @{ ok = $true; cancelled = $true } }
|
||||
return @{ ok = $true; path = $path }
|
||||
}
|
||||
|
||||
# App-Typ (@odata.type) -> unterstuetzter Content-Update-Pfad + erlaubte Endung.
|
||||
# Phase 1: nur win32LobApp/.intunewin aktiv; MSI/MSIX kommen in Phase 2/3.
|
||||
function Get-AppContentTypeMap {
|
||||
|
||||
Reference in New Issue
Block a user