# 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 }