Files
benoit 2212e25d31 Ajout de la langue anglaise
Ajout d'un README.md avec le descriptif et les instructions
L'ancien README.md s'appelle désormais CHANGELOG.md
Activation du pavé numérique par défaut pour les installations sans assistance
Nouveau design pour la fenêtre de déploiement.
Ajout de paramètre pour pouvoir procéder à une installation totalement automatisée
Ajout de paramètre pour copier au choix sources, drivers ou les deux sur C:\DeployVenter avant leur usage par DISM.
2026-10-18 04:14:17 +02:00

174 lines
9.7 KiB
PowerShell

# DeployCenter - Discover and generate Windows answer-file profiles.
# PSD1 profiles remain data files; answer files are built as 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 "Invalid Unattend profile name: $ProfileName" }
$path = Join-Path $ProfilesRoot ($ProfileName + '.psd1')
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Unattend profile not found: $ProfileName" }
$profile = Import-PowerShellDataFile -LiteralPath $path
foreach ($key in @('Id','Name','Description','Target','Template','ComputerName','LocalAccount')) {
if (-not $profile.ContainsKey($key)) { throw ('Unattend profile {0} does not contain key {1}.' -f $ProfileName, $key) }
}
if ([string]$profile.Id -ne $ProfileName) { throw ('Unattend profile ID {0} does not match the file name.' -f $ProfileName) }
if ([string]$profile.Target -notin @('Workstation', 'Server')) { throw ('The Target property of Unattend profile {0} must be Workstation or Server.' -f $ProfileName) }
if ([string]$profile.Template -notmatch '^[A-Za-z0-9_-]+\.xml$') { throw ('Invalid XML template name in profile {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 "XML template not found for profile '$ProfileName': $templatePath" }
if (-not [string]$profile.Name -or [string]::IsNullOrWhiteSpace([string]$profile.Name)) { throw ('Unattend profile {0} must define a display name.' -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 'The computer name must follow NetBIOS rules and not exceed 15 characters.'
}
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 '[Unattend] AdminName must be 1 to 20 characters and must not contain characters prohibited by Windows.' }
if ([string]::IsNullOrWhiteSpace($displayName)) { throw '[Unattend] AdminDisplayName cannot be empty.' }
if ([string]::IsNullOrWhiteSpace($groupName)) { throw '[Unattend] AdminGroup cannot be empty.' }
if ([string]::IsNullOrWhiteSpace($passwordBase64)) { throw 'The [Unattend] AdminPasswordBase64 setting is required for this profile.' }
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 'Invalid UTF-16LE Base64 format.'
}
} catch {
throw '[Unattend] AdminPasswordBase64 must be a UTF-16LE password encoded as 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 ('The template for profile {0} does not define the ComputerName node in 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 ('The template for profile {0} does not define the account field {1}.' -f $Profile.Id, $name) }
$node.InnerText = $values[$name]
}
}
$supportAppUrl = [string]$Configuration.UnattendSupportAppURL
$supportUrl = [string]$Configuration.UnattendSupportURL
# Compatibility: before SupportURL was added, a web URL could be provided in SupportAppURL.
if ($supportAppUrl -match '^https?://') {
if ([string]::IsNullOrWhiteSpace($supportUrl)) { $supportUrl = $supportAppUrl }
$supportAppUrl = ''
}
$supportSettings = @(
@{ Name = 'SupportProvider'; Value = [string]$Configuration.UnattendSupportProvider },
@{ Name = 'SupportURL'; Value = $supportUrl },
@{ Name = 'SupportAppURL'; Value = $supportAppUrl }
)
$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 ('The template for profile {0} does not define the {1} node.' -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 ('The template for profile {0} does not define the Shell-Setup component in 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