Files
DeployCenter/Modules/Unattend.psm1
T
benoit cb917c97ce Ajout d'un fond d'écran personnalisé si fourni dans DeployCenter.ini
Renommage du profil unattend pour mieux correspondre à ce qu'il fait.
Ajout d'un warning si WinRE n'est pas assez grands au regard des préconisations Microsoft et du redémarrage automatique si l'installation réussi.
2026-10-17 21:28:34 +02:00

166 lines
9.5 KiB
PowerShell

# DeployCenter - Découverte et génération des profils de réponses Windows.
# Les profils PSD1 restent des fichiers de données ; les réponses sont construites comme XML.
function Get-DeployCenterUnattendProfileNames {
[CmdletBinding()]
param([Parameter(Mandatory=$true)][string]$ProfilesRoot)
if (-not (Test-Path -LiteralPath $ProfilesRoot -PathType Container)) { return @() }
$names = @()
foreach ($file in (Get-ChildItem -LiteralPath $ProfilesRoot -Filter '*.psd1' -File -ErrorAction SilentlyContinue)) {
if ($file.BaseName -match '^[A-Za-z0-9_-]+$') { $names += $file.BaseName }
}
return @($names | Sort-Object -Unique)
}
function Import-DeployCenterUnattendProfile {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$ProfilesRoot,
[Parameter(Mandatory=$true)][string]$ProfileName
)
if ($ProfileName -notmatch '^[A-Za-z0-9_-]+$') { throw "Nom de profil Unattend invalide : $ProfileName" }
$path = Join-Path $ProfilesRoot ($ProfileName + '.psd1')
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Profil Unattend introuvable : $ProfileName" }
$profile = Import-PowerShellDataFile -LiteralPath $path
foreach ($key in @('Id','Name','Description','Target','Template','ComputerName','LocalAccount')) {
if (-not $profile.ContainsKey($key)) { throw ('Le profil Unattend {0} ne contient pas la cle {1}.' -f $ProfileName, $key) }
}
if ([string]$profile.Id -ne $ProfileName) { throw ('L identifiant du profil Unattend {0} ne correspond pas au nom du fichier.' -f $ProfileName) }
if ([string]$profile.Target -notin @('Workstation', 'Server')) { throw ('La propriete Target du profil Unattend {0} doit etre Workstation ou Server.' -f $ProfileName) }
if ([string]$profile.Template -notmatch '^[A-Za-z0-9_-]+\.xml$') { throw ('Nom de template XML invalide dans le profil {0}.' -f $ProfileName) }
$templatesRoot = Join-Path (Split-Path -Parent $ProfilesRoot) 'Templates'
$templatePath = Join-Path $templatesRoot ([string]$profile.Template)
if (-not (Test-Path -LiteralPath $templatePath -PathType Leaf)) { throw "Template XML introuvable pour le profil '$ProfileName' : $templatePath" }
if (-not [string]$profile.Name -or [string]::IsNullOrWhiteSpace([string]$profile.Name)) { throw ('Le profil Unattend {0} doit definir un nom affichable.' -f $ProfileName) }
$profile.TemplatePath = $templatePath
return $profile
}
function Test-DeployCenterUnattendConfiguration {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Profile,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Configuration,
[Parameter(Mandatory=$true)][string]$HostName
)
if ($Profile.ComputerName -and $HostName -notmatch '^(?=.*[A-Za-z])[A-Za-z0-9](?:[A-Za-z0-9-]{0,13}[A-Za-z0-9])?$') {
throw 'Le nom de machine doit respecter les règles NetBIOS et ne pas dépasser 15 caractères.'
}
if ($Profile.LocalAccount) {
$accountName = [string]$Configuration.UnattendAdminName
$displayName = [string]$Configuration.UnattendAdminDisplayName
$groupName = [string]$Configuration.UnattendAdminGroup
$passwordBase64 = [string]$Configuration.UnattendAdminPasswordBase64
if ($accountName -notmatch '^[^"/\[\]:;|=,+*?<>\\]{1,20}$') { throw 'Le paramètre [Unattend] AdminName doit contenir de 1 à 20 caractères et éviter les caractères interdits par Windows.' }
if ([string]::IsNullOrWhiteSpace($displayName)) { throw 'Le paramètre [Unattend] AdminDisplayName ne peut pas être vide.' }
if ([string]::IsNullOrWhiteSpace($groupName)) { throw 'Le paramètre [Unattend] AdminGroup ne peut pas être vide.' }
if ([string]::IsNullOrWhiteSpace($passwordBase64)) { throw 'Le paramètre [Unattend] AdminPasswordBase64 est requis pour ce profil.' }
try {
$passwordBytes = [Convert]::FromBase64String($passwordBase64)
if ($passwordBytes.Length -eq 0 -or ($passwordBytes.Length % 2) -ne 0 -or
[string]::IsNullOrEmpty([Text.Encoding]::Unicode.GetString($passwordBytes))) {
throw 'Format UTF-16LE Base64 invalide.'
}
} catch {
throw 'Le paramètre [Unattend] AdminPasswordBase64 doit être un mot de passe UTF-16LE encodé en Base64.'
}
}
}
function New-DeployCenterUnattendXml {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Profile,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Configuration,
[Parameter(Mandatory=$true)][string]$HostName,
[Parameter(Mandatory=$true)][string]$Architecture,
[AllowEmptyString()][string]$WallpaperPath = ''
)
Test-DeployCenterUnattendConfiguration -Profile $Profile -Configuration $Configuration -HostName $HostName
$document = New-Object System.Xml.XmlDocument
$document.PreserveWhitespace = $true
$document.Load([string]$Profile.TemplatePath)
$namespaces = New-Object System.Xml.XmlNamespaceManager($document.NameTable)
$namespaces.AddNamespace('u', 'urn:schemas-microsoft-com:unattend')
$namespaces.AddNamespace('wcm', 'http://schemas.microsoft.com/WMIConfig/2002/State')
$processorArchitecture = switch -Regex ($Architecture.ToLowerInvariant()) {
'arm64' { 'arm64'; break }
'x86|32' { 'x86'; break }
default { 'amd64' }
}
foreach ($component in $document.SelectNodes('//u:component', $namespaces)) {
$component.SetAttribute('processorArchitecture', $processorArchitecture)
}
if ($Profile.ComputerName) {
$node = $document.SelectSingleNode('/u:unattend/u:settings[@pass="specialize"]/u:component[@name="Microsoft-Windows-Shell-Setup"]/u:ComputerName', $namespaces)
if ($null -eq $node) { throw ('Le template du profil {0} ne definit pas le noeud ComputerName dans specialize.' -f $Profile.Id) }
$node.InnerText = $HostName
}
if ($Profile.LocalAccount) {
$values = @{
Name = [string]$Configuration.UnattendAdminName
DisplayName = [string]$Configuration.UnattendAdminDisplayName
Group = [string]$Configuration.UnattendAdminGroup
Password = [string]$Configuration.UnattendAdminPasswordBase64
}
foreach ($name in $values.Keys) {
$field = if ($name -eq 'Password') { 'Password/u:Value' } else { $name }
$node = $document.SelectSingleNode("/u:unattend/u:settings[@pass='oobeSystem']/u:component[@name='Microsoft-Windows-Shell-Setup']/u:UserAccounts/u:LocalAccounts/u:LocalAccount/u:$field", $namespaces)
if ($null -eq $node) { throw ('Le template du profil {0} ne definit pas le champ de compte {1}.' -f $Profile.Id, $name) }
$node.InnerText = $values[$name]
}
}
$supportSettings = @(
@{ Name = 'SupportProvider'; Value = [string]$Configuration.UnattendSupportProvider },
@{ Name = 'SupportAppURL'; Value = [string]$Configuration.UnattendSupportAppURL }
)
$oemInformationPath = '/u:unattend/u:settings[@pass="oobeSystem"]/u:component[@name="Microsoft-Windows-Shell-Setup"]/u:OEMInformation'
foreach ($supportSetting in $supportSettings) {
$supportNode = $document.SelectSingleNode(($oemInformationPath + '/u:' + $supportSetting.Name), $namespaces)
if ($null -eq $supportNode) {
if (-not [string]::IsNullOrWhiteSpace($supportSetting.Value)) {
throw ('Le template du profil {0} ne definit pas le noeud {1}.' -f $Profile.Id, $supportSetting.Name)
}
continue
}
if ([string]::IsNullOrWhiteSpace($supportSetting.Value)) {
[void]$supportNode.ParentNode.RemoveChild($supportNode)
} else {
$supportNode.InnerText = $supportSetting.Value
}
}
$oemInformationNode = $document.SelectSingleNode($oemInformationPath, $namespaces)
if ($null -ne $oemInformationNode -and $oemInformationNode.SelectNodes('u:*', $namespaces).Count -eq 0) {
[void]$oemInformationNode.ParentNode.RemoveChild($oemInformationNode)
}
if (-not [string]::IsNullOrWhiteSpace($WallpaperPath)) {
$shellSetupNode = $document.SelectSingleNode('/u:unattend/u:settings[@pass="oobeSystem"]/u:component[@name="Microsoft-Windows-Shell-Setup"]', $namespaces)
if ($null -eq $shellSetupNode) { throw ('Le template du profil {0} ne definit pas le composant Shell-Setup dans oobeSystem.' -f $Profile.Id) }
$themesNode = $shellSetupNode.SelectSingleNode('u:Themes', $namespaces)
if ($null -eq $themesNode) {
$themesNode = $document.CreateElement('Themes', 'urn:schemas-microsoft-com:unattend')
[void]$shellSetupNode.AppendChild($themesNode)
}
foreach ($themeSetting in @(
@{ Name = 'ThemeName'; Value = 'Blorand' },
@{ Name = 'DesktopBackground'; Value = $WallpaperPath },
@{ Name = 'UWPAppsUseLightTheme'; Value = 'true' }
)) {
$themeNode = $themesNode.SelectSingleNode(('u:' + $themeSetting.Name), $namespaces)
if ($null -eq $themeNode) {
$themeNode = $document.CreateElement($themeSetting.Name, 'urn:schemas-microsoft-com:unattend')
[void]$themesNode.AppendChild($themeNode)
}
$themeNode.InnerText = [string]$themeSetting.Value
}
}
return $document.OuterXml
}
Export-ModuleMember -Function Get-DeployCenterUnattendProfileNames, Import-DeployCenterUnattendProfile, Test-DeployCenterUnattendConfiguration, New-DeployCenterUnattendXml