Files
lgpo-server/scripts/Export-And-Publish.ps1
groot 6ebdcd4fb0
All checks were successful
release-tag / release-image (push) Successful in 1m30s
Bugfix
2026-08-05 12:01:06 +02:00

129 lines
4.4 KiB
PowerShell

#requires -Modules GroupPolicy
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $GpoName,
[string] $PolicyName = '',
[Parameter(Mandatory)] [string] $ServerUrl,
[Parameter(Mandatory)] [string] $AdminToken,
[Parameter(Mandatory)] [string] $GpoCtl,
[string] $Note = '',
[string] $Domain,
[string] $DomainController,
[switch] $InsecureSkipVerify,
[switch] $ForceVersion,
[switch] $StrictPolicyName
)
$ErrorActionPreference = 'Stop'
$policyNamePattern = '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
function ConvertTo-RepositoryPolicyName {
[CmdletBinding()]
param([Parameter(Mandatory)] [string] $Value)
$valueToConvert = $Value.Trim()
if ([string]::IsNullOrWhiteSpace($valueToConvert)) {
throw 'PolicyName must not be empty.'
}
# Remove diacritics where possible and replace every sequence of characters
# that is unsafe for a repository identifier with a single hyphen.
$decomposed = $valueToConvert.Normalize([Text.NormalizationForm]::FormD)
$builder = [Text.StringBuilder]::new()
$separatorPending = $false
foreach ($character in $decomposed.ToCharArray()) {
$category = [Globalization.CharUnicodeInfo]::GetUnicodeCategory($character)
if ($category -eq [Globalization.UnicodeCategory]::NonSpacingMark) {
continue
}
$text = [string] $character
if ($text -match '^[A-Za-z0-9._-]$') {
[void] $builder.Append($character)
$separatorPending = $false
}
elseif (-not $separatorPending -and $builder.Length -gt 0) {
[void] $builder.Append('-')
$separatorPending = $true
}
}
$converted = $builder.ToString()
$converted = $converted -replace '-{2,}', '-'
$converted = $converted.Trim([char[]]'.-_')
if ([string]::IsNullOrWhiteSpace($converted)) {
throw "PolicyName '$Value' cannot be converted to a valid repository name."
}
if ($converted[0] -notmatch '[A-Za-z0-9]') {
$converted = 'gpo-' + $converted
}
if ($converted.Length -gt 64) {
$converted = $converted.Substring(0, 64).TrimEnd([char[]]'.-_')
}
if ($converted -notmatch $policyNamePattern) {
throw "PolicyName '$Value' cannot be converted to a name matching $policyNamePattern."
}
return $converted
}
if (-not (Test-Path -LiteralPath $GpoCtl -PathType Leaf)) {
throw "gpoctl not found: $GpoCtl"
}
$requestedPolicyName = if ([string]::IsNullOrWhiteSpace($PolicyName)) { $GpoName } else { $PolicyName.Trim() }
if ($requestedPolicyName -match $policyNamePattern) {
$resolvedPolicyName = $requestedPolicyName
}
elseif ($StrictPolicyName) {
throw "PolicyName '$requestedPolicyName' is invalid. It must match $policyNamePattern (maximum 64 characters; no spaces or umlauts)."
}
else {
$resolvedPolicyName = ConvertTo-RepositoryPolicyName -Value $requestedPolicyName
Write-Warning "PolicyName '$requestedPolicyName' was normalized to '$resolvedPolicyName'."
}
Write-Host "Publishing AD GPO '$GpoName' as repository policy '$resolvedPolicyName'."
$work = Join-Path ([IO.Path]::GetTempPath()) ("gpo-publish-" + [guid]::NewGuid().ToString('N'))
$backup = Join-Path $work 'backup'
$zip = Join-Path $work 'gpo-backup.zip'
New-Item -ItemType Directory -Force -Path $backup | Out-Null
try {
$params = @{ Name = $GpoName; Path = $backup }
if ($Note) { $params.Comment = $Note }
if ($Domain) { $params.Domain = $Domain }
if ($DomainController) { $params.Server = $DomainController }
Backup-GPO @params | Out-Null
# CreateFromDirectory includes hidden files such as bkupInfo.xml. The ZIP root
# contains manifest.xml plus the GUID-named GPO backup directory expected by LGPO.exe.
Add-Type -AssemblyName System.IO.Compression.FileSystem
[IO.Compression.ZipFile]::CreateFromDirectory(
$backup,
$zip,
[IO.Compression.CompressionLevel]::Optimal,
$false
)
$arguments = @(
'upload',
'-server', $ServerUrl,
'-token', $AdminToken,
'-policy', $resolvedPolicyName,
'-file', $zip
)
if ($Note) { $arguments += @('-note', $Note) }
if ($InsecureSkipVerify) { $arguments += '-insecure-skip-verify' }
if ($ForceVersion) { $arguments += '-force' }
& $GpoCtl @arguments
if ($LASTEXITCODE -ne 0) {
throw "gpoctl exited with code $LASTEXITCODE"
}
}
finally {
Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue
}