# 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 ) 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] } } return $document.OuterXml } Export-ModuleMember -Function Get-DeployCenterUnattendProfileNames, Import-DeployCenterUnattendProfile, Test-DeployCenterUnattendConfiguration, New-DeployCenterUnattendXml