quacksalber
Goto Top

Hilfestellung PowerShell Script

Hi zusammen,

ich würde gerne folgendes erreichen. Über ein PowerShell Script möchte ich GPOs in Massen im- und exportieren können, sowie nicht verlinkte GPOs löschen. Mit meinem PowerShell Script kann ich sowohl Exportieren als auch Löschen, aber das Importieren klappt einfach nicht.

Ich habe einen Stammordner der den Namen der GPO trägt, wonach auch eine importierte GPO dann benannt werden soll.
In diesem Stammordner gibt es einen Unterordner, der vermutlich nach der ID der GPO benannt ist, da der einen kryptischen Namen hat. Innerhalb des Unterordner befindet sich dann die Backup.xml. Diese Backup.xml ist der Grund warum ich nicht importieren kann, da das Script diese Datei nicht findet. Ich habe vieles probiert und auch rekursiv als Parameter mit angegeben, damit er auch die Unterordner durchsucht, aber es funktioniert einfach nicht.
So bin ich nun auf euer Schwarmwissen angewiesen. Anbei mein Script, vielleicht findet sich ja jemand, der den Fehler findet oder mir zumindestens eine Hilfestellung geben kann.

Quacksalber

# PowerShell-Skript zum Exportieren und Importieren aller GPOs mit Logdatei, unter Auslassung bestimmter GPOs

# Prüfen, ob das Skript mit Administratorrechten ausgeführt wird
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Host "Bitte starten Sie das Skript mit Administratorrechten." -ForegroundColor Red  
    Exit
}

# Importieren des Group Policy PowerShell-Moduls
Import-Module GroupPolicy

# Festlegen der Pfade für Export, Import und Logdatei
$ExportPath = "C:\test\Scripte\GPO-Import\Exportierte GPOs"  
$ImportPath = "C:\test\Scripte\GPO-Import\Importierende GPOs"  
$LogPath = "C:\test\Scripte\GPO-Import\Logs"  

# Erstellen des Log-Dateinamens mit Zeitstempel
$timestampForFile = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"  
$LogFileName = "GPO-Log_$timestampForFile.txt"  
$LogFilePath = Join-Path -Path $LogPath -ChildPath $LogFileName

# GPOs, die nicht exportiert werden sollen
$ExcludeGPOs = @("Default Domain Policy", "Default Domain Controllers Policy")  

# Funktion zum Schreiben in die Logdatei
function Write-Log {
    param (
        [string]$Message
    )

    # Überprüfen und Erstellen des Log-Verzeichnisses, falls es nicht existiert
    if (-not (Test-Path -Path $LogPath -PathType Container)) {

        New-Item -ItemType Directory -Path $LogPath
    }

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"  
    Add-Content -Path $LogFilePath -Value "[$timestamp] $Message"  
}

# Funktion zum Exportieren aller GPOs
function Export-AllGPOs {
    Get-GPO -All | Where-Object { $ExcludeGPOs -notcontains $_.DisplayName } | ForEach-Object {
        $GPOName = $_.DisplayName
        $GPOId = $_.Id
        $FullPath = $ExportPath + "\" + $GPOName  

        if (-not (Test-Path -Path $FullPath)) {
            New-Item -ItemType Directory -Path $FullPath
        }

        try {
            Backup-GPO -Guid $GPOId -Path $FullPath
            Write-Log "GPO wurde erfolgreich exportiert: $GPOName -> $FullPath"  
        } catch {
            $errorMessage = "Fehler beim Exportieren der GPO '$GPOName': $_"  
            Write-Log $errorMessage
            Write-Host $errorMessage -ForegroundColor Red
        }
    }
}



function Import-AllGPOs {
    Get-ChildItem -Path $ImportPath -Directory | ForEach-Object {
        $GPOName = $_.Name
        $FullPath = $_.FullName

        # Finden der BackupId im Importordner
        $BackupId = (Get-ChildItem -Path $FullPath -Filter "Backup.xml").FullName  
        if ($BackupId) {
            $BackupId = [xml](Get-Content $BackupId)
            $BackupId = $BackupId.BackupGpo.BackupId
        } else {
            Write-Log "Fehler: Keine Backup.xml für GPO '$GPOName' in '$FullPath' gefunden."  
            return
        }

        if (Test-Path -Path $FullPath) {
            try {
                $NewGPO = New-GPO -Name $GPOName
                Restore-GPO -BackupId $BackupId -TargetName $NewGPO.DisplayName -Path $FullPath -CreateIfNeeded
                Write-Log "GPO wurde erfolgreich importiert: $GPOName"  
            } catch {
                Write-Log "Fehler beim Importieren der GPO '$GPOName': $_"  
            }
        } else {
            Write-Log "Fehler: Der Importpfad $FullPath konnte nicht gefunden werden."  
        }
    }
}

# Funktion zum Löschen nicht verknüpfter GPOs
function Remove-UnlinkedGPOs {
    $GPOs = Get-GPO -All
    foreach ($GPO in $GPOs) {
        $GPOReport = Get-GPOReport -Guid $GPO.Id -ReportType Xml
        $GPOReportXml = [xml]$GPOReport

        # Überprüfen, ob die GPO Verknüpfungen hat
        $links = $GPOReportXml.GPO.LinksTo
        if ($null -eq $links) {
            try {
                Remove-GPO -Guid $GPO.Id -Confirm:$false
                Write-Log "Nicht verknüpfte GPO wurde gelöscht: $($GPO.DisplayName)"  
            } catch {
                Write-Log "Fehler beim Löschen der nicht verknüpften GPO: $($GPO.DisplayName) - $_"  
            }
        }
    }
}


# Benutzerauswahl für Export oder Import
$auswahl = Read-Host "Wählen Sie eine Option: (E)xportieren aller GPOs, (I)mportieren aller GPOs oder (L)öschen aller nicht verknüpften GPOs"  

switch ($auswahl.ToUpper()) {
    "E" {  
        Export-AllGPOs
    }
    "I" {  
        Import-AllGPOs
    }
    "L" {  
        Remove-UnlinkedGPOs
    }
    default {
        Write-Host "Ungültige Auswahl. Bitte wähle 'E' für Export, 'I' für Import oder 'L' für Löschen"  
    }
}


Write-Host "Vorgang abgeschlossen. Überprüfen Sie die Logdatei unter $LogFilePath für Details."  

Content-Key: 7688751462

Url: https://administrator.de/contentid/7688751462

Printed on: April 23, 2024 at 23:04 o'clock

Mitglied: 11078840001
11078840001 Feb 21, 2024 updated at 15:35:53 (UTC)
Goto Top
$backupId = (Get-Item -Path "$FullPath\*\Backup.xml").FullName  
☠️
Member: Quacksalber
Quacksalber Feb 21, 2024 at 15:39:07 (UTC)
Goto Top
Danke für deine Antwort! Habe Zeile 71 mit deine Code ausgetauscht, jedoch bekomm ich im Log immer noch die Meldung, dass er die Backup.xml nicht finden kann:

[2024-02-21 16:35:18] Fehler: Keine Backup.xml für GPO 'TEST-GPO' in 'C:\test\Scripte\GPO-Import\Importierende GPOs\TEST-GPO' gefunden.
Mitglied: 11078840001
11078840001 Feb 21, 2024 updated at 15:51:18 (UTC)
Goto Top
Hier kennt ja keiner deine Ordner-Struktur im Import-Ordner wenn da eine Ebene tiefer keine Backup.xml liegt dann liegt da auch keine in deinem uns geschilderten Verzeichnis oder du hast keine Zugriffsrechte auf den Ordner.
Klappt hier problemlos guckst du.
https://tio.run/##TYvLCoMwEEX38xVDKKiFJPS567bL/oOPUYMTG0xCC@K3py266N1c7u ...
Member: Quacksalber
Quacksalber Feb 21, 2024 at 15:52:09 (UTC)
Goto Top
In dem Unterordner befindet sich die Backup.xml:
C:\test\Scripte\GPO-Import\Importierende GPOs\TEST-GPO\{27038F39-7F07-4839-8EE3-E0E69F42BE45}\Backup.xml
Member: Quacksalber
Quacksalber Feb 21, 2024 at 15:52:55 (UTC)
Goto Top
Zitat von @Quacksalber:

In dem Unterordner befindet sich die Backup.xml:
C:\test\Scripte\GPO-Import\Importierende GPOs\TEST-GPO\{27038F39-7F07-4839-8EE3-E0E69F42BE45}\Backup.xml

Könnte es an den geschweiften Klammern liegen?
Mitglied: 11078840001
11078840001 Feb 21, 2024 updated at 15:58:00 (UTC)
Goto Top
Nein, wenn es eckige ( [ ] ) im Pfad gäbe dann eher so aber nicht.
https://tio.run/##TYxLCoMwGIT3OUUIBbUQFbVVF90UkmXv4ONXg4kVk9CCePbUUhed1c ...
Member: Quacksalber
Quacksalber Feb 21, 2024 at 15:58:17 (UTC)
Goto Top
Zitat von @11078840001:

Nein, wenn es eckige ( [ ] ) wären dann ja.
https://tio.run/##TYxLCoMwGIT3OUUIBbUQFbVVF90UkmXv4ONXg4kVk9CCePbUUhed1c ...

Ich habe die Importfunktion angepasst und "-LiteralPath" genutzt. Die GPOs werden jetzt importiert, jedoch ohne Inhalt. "Der Parametersatz kann mit den angegebenen benannten Parametern nicht aufgelöst werden." lautet nun die Fehlermeldung..
Mitglied: 11078840001
11078840001 Feb 21, 2024 updated at 16:50:18 (UTC)
Goto Top
"Der Parametersatz kann mit den angegebenen benannten Parametern nicht aufgelöst werden."
RTFM ! Restore-GPO

-TargetName gibbet ned
-CreateIfNeeded gibbet ned

Du meinst wohl Import-GPO und das New-GPO ist dann überflüssig wenn CreateIfNeeded benutzt wird.

function Import-AllGPOs {
    foreach($gpo in Get-ChildItem -Path $ImportPath -Directory){
        $BackupId = (Get-Item "$($gpo.Fullname)\{*}" | select -First 1).Name  
        if (!$BackupId){
            Write-Log "Fehler: Kein Backup in '$($gpo.Fullname)' gefunden."  
            continue
        }
        try {
            Import-GPO -BackupId $BackupId -Path $gpo.Fullname -TargetName $gpo.Name -CreateIfNeeded -ErrorAction Stop
            Write-Log "GPO wurde erfolgreich importiert: $($gpo.Name)"    
        } catch {
            Write-Log "Fehler beim Importieren der GPO '$($gpo.Name)': $($_.Exception.Message)"    
        } 
    }
}
Member: Quacksalber
Quacksalber Feb 21, 2024 at 16:59:37 (UTC)
Goto Top
Zitat von @11078840001:

"Der Parametersatz kann mit den angegebenen benannten Parametern nicht aufgelöst werden."
RTFM ! Restore-GPO

Ich finde die Microsoft Seiten echt unübersichtlich, bin auch nicht so in der PowerShell zuhause, vielleicht fällt es mir daher etwas schwerer

-TargetName gibbet ned
-CreateIfNeeded gibbet ned

Du meinst wohl Import-GPO und das New-GPO ist dann überflüssig wenn CreateIfNeeded benutzt wird.

function Import-AllGPOs {
    foreach($gpo in Get-ChildItem -Path $ImportPath -Directory){
        $BackupId = (Get-Item "$($gpo.Fullname)\{*}" | select -First 1).Name  
        if (!$BackupId){
            Write-Log "Fehler: Kein Backup in '$($gpo.Fullname)' gefunden."  
            continue
        }
        try {
            Import-GPO -BackupId $BackupId -Path $gpo.Fullname -TargetName $gpo.Name -CreateIfNeeded -ErrorAction Stop
            Write-Log "GPO wurde erfolgreich importiert: $($gpo.Name)"    
        } catch {
            Write-Log "Fehler beim Importieren der GPO '$($gpo.Name)': $($_.Exception.Message)"    
        } 
    }
}

Wenn ich deine Importfunktion nutze, steht im Log nur "[2024-02-21 17:51:58] Fehler: Kein Backup in 'C:\test\Scripte\GPO-Import\Importierende GPOs\TEST-GPO' gefunden."
Er versucht nur eine GPO zu importieren, wobei ich in dem Ordner "Importierende GPOs" ca. 30 GPOs vorliegen habe.

Mit meiner neuen Importfunkion
# Funktion zum Exportieren aller GPOs
function Export-AllGPOs {
    Get-GPO -All | Where-Object { $ExcludeGPOs -notcontains $_.DisplayName } | ForEach-Object {
        $GPOName = $_.DisplayName
        $GPOId = $_.Id
        $FullPath = $ExportPath + "\" + $GPOName  

        if (-not (Test-Path -Path $FullPath)) {
            New-Item -ItemType Directory -Path $FullPath
        }

        try {
            Backup-GPO -Guid $GPOId -Path $FullPath
            Write-Log "GPO wurde erfolgreich exportiert: $GPOName -> $FullPath"  
        } catch {
            $errorMessage = "Fehler beim Exportieren der GPO '$GPOName': $_"  
            Write-Log $errorMessage
            Write-Host $errorMessage -ForegroundColor Red
        }
    }
}

Importiert er zwar alle, aber eben ohne Inhalt. Fehlermeldung lautet dann in dem Fall "[2024-02-21 17:57:03] Fehler beim Importieren der GPO 'GPO-TEST': Das Argument für den Parameter "BackupId" kann nicht überprüft werden. Das Argument ist NULL oder leer. Geben Sie ein Argument an, das nicht NULL oder leer ist, und führen Sie den Befehl erneut aus."
Mitglied: 11078840001
11078840001 Feb 23, 2024 updated at 10:17:59 (UTC)
Goto Top
Wenn ich deine Importfunktion nutze, steht im Log nur "[2024-02-21 17:51:58] Fehler: Kein Backup in 'C:\test\Scripte\GPO-Import\Importierende GPOs\TEST-GPO' gefunden."
Nö klappt hier völlig problemlos in meinem Test, hast du wohl ein Fehler beim Kopieren gemacht ...

Mit meiner neuen Importfunkion
function Export-AllGPOs {

Je nee is klar 😂

Machen wir dem Trauerspiel mal ein Ende

function Export-GPOs {
    param(
        [parameter(mandatory=$true)][string]$path,
        [parameter(mandatory=$false)][string[]]$exclude

    )
    if (!(Test-Path $path)){New-Item -Type Dir -Path $path | out-null}

    foreach($gpo in Get-GPO -All | ? DisplayName -notin $exclude){
        $savepath = join-path $path $gpo.DisplayName
        if (!(Test-Path $savepath)){New-Item -Type Dir -Path $savepath | out-null}
        Backup-GPO -Guid $gpo.Id -Path $savepath
    }
}

function Import-GPOs {
    param(
        [parameter(mandatory=$true)][ValidateScript({Test-Path -LiteralPath $_ -PathType Container})][string]$path,
        [parameter(mandatory=$false)][string[]]$exclude

    )
    foreach($gpofolder in Get-ChildItem -Path $path -Directory -Exclude $exclude){
        $backupid = (Get-Item "$($gpofolder.Fullname)\{*}").Name  
        if(!$backupid){
            write-error "Folder '$($gpofolder.Name)' does not contain a GPO!" -Category ObjectNotFound  
            continue
        }
        Import-GPO -BackupId $backupid -Path $gpofolder.Fullname -TargetName $gpofolder.Name -CreateIfNeeded
    }
}

Export-GPOs -Path "$home\Desktop\GPO-Backup" -exclude "Default Domain Controllers Policy","Default Domain Policy"  
Import-GPOs -Path "$home\Desktop\GPO-Backup" -exclude "Default Domain Controllers Policy","Default Domain Policy"  
Klappt hier absolut problemlos.


me => out 🖖