declarative-ad-framework/gpo/lib/GPOFolderRedirection.ps1
Damien Coles f172d00514 Initial release: Declarative AD Framework v2.1.0
Infrastructure-as-code framework for Active Directory objects and Group Policy.
Sanitized from production deployment for public sharing.
2026-02-19 17:02:42 +00:00

216 lines
7.2 KiB
PowerShell

# GPOFolderRedirection.ps1
# Folder Redirection management via GPO.
# Writes fdeploy1.ini to SYSVOL and registers the CSE extension GUIDs.
# Depends on: GPOCore.ps1 (Get-GPOSysvolPath, Add-GPOExtensionGuids)
$Script:FolderRedirectionCseGuid = '{25537BA6-77A8-11D2-9B6C-0000F8080861}'
$Script:FolderRedirectionToolGuid = '{88E729D6-BDC1-11D1-BD2A-00C04FB9603F}'
# Maps settings.ps1 key names to fdeploy1.ini section names
$Script:FolderSectionMap = @{
Desktop = '.Desktop'
Documents = '.Documents'
Pictures = '.Pictures'
Music = '.Music'
Videos = '.Videos'
Favorites = '.Favorites'
AppDataRoaming = '.AppData'
Contacts = '.Contacts'
Downloads = '.Downloads'
Links = '.Links'
Searches = '.Searches'
StartMenu = '.StartMenu'
}
function ConvertTo-FolderRedirectionIni {
<#
.SYNOPSIS
Converts a FolderRedirection hashtable into fdeploy1.ini content.
#>
param(
[Parameter(Mandatory)]
[hashtable]$FolderRedirection
)
$sections = foreach ($folderName in $FolderRedirection.Keys) {
$folder = $FolderRedirection[$folderName]
$sectionName = $Script:FolderSectionMap[$folderName]
if (-not $sectionName) {
Write-Host " [WARN] Unknown folder: $folderName" -ForegroundColor Yellow
continue
}
$destPath = $folder.Path
$grantExclusive = if ($folder.GrantExclusive) { '0x00000001' } else { '0x00000000' }
$moveContents = if ($folder.MoveContents) { '0x00000001' } else { '0x00000000' }
# Status=0x0001 = basic redirection (follow this folder)
@"
[$sectionName]
Status=0x0001
DestPath=$destPath
GrantExclusive=$grantExclusive
MoveContents=$moveContents
"@
}
return ($sections -join "`r`n`r`n") + "`r`n"
}
function Set-GPOFolderRedirection {
<#
.SYNOPSIS
Writes fdeploy1.ini to SYSVOL for folder redirection.
Full overwrite semantics. User scope only.
#>
param(
[Parameter(Mandatory)]
[string]$GPOName,
[Parameter(Mandatory)]
[hashtable]$FolderRedirection,
[string]$Domain = (Get-ADDomain).DNSRoot
)
Write-Host " Applying folder redirection..." -ForegroundColor Yellow
$ini = ConvertTo-FolderRedirectionIni -FolderRedirection $FolderRedirection
$sysvolPath = Get-GPOSysvolPath -GPOName $GPOName -Domain $Domain
$deployDir = Join-Path $sysvolPath 'User\Documents & Settings'
if (-not (Test-Path $deployDir)) {
New-Item -ItemType Directory -Path $deployDir -Force | Out-Null
}
$iniPath = Join-Path $deployDir 'fdeploy1.ini'
# Write as UTF-16LE (same encoding as other INI files in SYSVOL)
$utf16le = [System.Text.UnicodeEncoding]::new($false, $true)
[System.IO.File]::WriteAllText($iniPath, $ini, $utf16le)
Write-Host " Written: User\Documents & Settings\fdeploy1.ini" -ForegroundColor Green
# Register CSE extension GUIDs
Add-GPOExtensionGuids -GPOName $GPOName `
-CseGuid $Script:FolderRedirectionCseGuid `
-ToolGuid $Script:FolderRedirectionToolGuid `
-Scope User -Domain $Domain
# Report what was configured
foreach ($folderName in $FolderRedirection.Keys) {
Write-Host " $folderName -> $($FolderRedirection[$folderName].Path)" -ForegroundColor Green
}
}
function Compare-GPOFolderRedirection {
<#
.SYNOPSIS
Compares desired folder redirection against current fdeploy1.ini in SYSVOL.
#>
param(
[Parameter(Mandatory)]
[string]$GPOName,
[Parameter(Mandatory)]
[hashtable]$FolderRedirection,
[string]$Domain = (Get-ADDomain).DNSRoot
)
$diffs = @()
Write-Host " Comparing folder redirection..." -ForegroundColor Yellow
$sysvolPath = Get-GPOSysvolPath -GPOName $GPOName -Domain $Domain
$iniPath = Join-Path $sysvolPath 'User\Documents & Settings\fdeploy1.ini'
if (-not (Test-Path $iniPath)) {
Write-Host " [DRIFT] Missing: fdeploy1.ini" -ForegroundColor Red
$diffs += [PSCustomObject]@{
Type = 'FolderRedirection'
Folder = '(all)'
Status = 'File missing'
}
return $diffs
}
# Parse existing INI
$currentContent = Get-Content $iniPath -Raw
$currentSections = @{}
$currentSection = $null
foreach ($line in ($currentContent -split "`r?`n")) {
$trimmed = $line.Trim()
if ($trimmed -match '^\[(.+)\]$') {
$currentSection = $Matches[1]
$currentSections[$currentSection] = @{}
} elseif ($currentSection -and $trimmed -match '^(.+?)=(.*)$') {
$currentSections[$currentSection][$Matches[1]] = $Matches[2]
}
}
foreach ($folderName in $FolderRedirection.Keys) {
$desired = $FolderRedirection[$folderName]
$sectionName = $Script:FolderSectionMap[$folderName]
if (-not $sectionName) { continue }
if (-not $currentSections.ContainsKey($sectionName)) {
Write-Host " [DRIFT] Missing section: $sectionName ($folderName)" -ForegroundColor Red
$diffs += [PSCustomObject]@{
Type = 'FolderRedirection'
Folder = $folderName
Status = 'Missing section'
}
continue
}
$section = $currentSections[$sectionName]
# Check DestPath
$currentPath = $section['DestPath']
if ($currentPath -ne $desired.Path) {
$pathDisplay = if ($currentPath) { $currentPath } else { '(not set)' }
Write-Host " [DRIFT] $folderName DestPath: $pathDisplay -> $($desired.Path)" -ForegroundColor Red
$diffs += [PSCustomObject]@{
Type = 'FolderRedirection'
Folder = $folderName
Status = "DestPath: $pathDisplay -> $($desired.Path)"
}
} else {
Write-Host " [OK] $folderName -> $currentPath" -ForegroundColor Green
}
# Check GrantExclusive
$desiredGrant = if ($desired.GrantExclusive) { '0x00000001' } else { '0x00000000' }
if ($section['GrantExclusive'] -ne $desiredGrant) {
Write-Host " [DRIFT] $folderName GrantExclusive: $($section['GrantExclusive']) -> $desiredGrant" -ForegroundColor Red
$diffs += [PSCustomObject]@{
Type = 'FolderRedirection'
Folder = $folderName
Status = "GrantExclusive mismatch"
}
}
# Check MoveContents
$desiredMove = if ($desired.MoveContents) { '0x00000001' } else { '0x00000000' }
if ($section['MoveContents'] -ne $desiredMove) {
Write-Host " [DRIFT] $folderName MoveContents: $($section['MoveContents']) -> $desiredMove" -ForegroundColor Red
$diffs += [PSCustomObject]@{
Type = 'FolderRedirection'
Folder = $folderName
Status = "MoveContents mismatch"
}
}
}
if ($diffs.Count -eq 0) {
Write-Host " [OK] Folder redirection matches desired state" -ForegroundColor Green
} else {
Write-Host " [DRIFT] $($diffs.Count) folder redirection difference(s) found" -ForegroundColor Red
}
return $diffs
}