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.
166 lines
9.5 KiB
PowerShell
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
|