Infrastructure-as-code framework for Active Directory objects and Group Policy. Sanitized from production deployment for public sharing.
216 lines
7.2 KiB
PowerShell
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
|
|
}
|