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.
174 lines
9.7 KiB
PowerShell
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
|