DeployCenter
This commit is contained in:
commit
7c724d475e
14 files changed
+2460
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
Logs/*
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 487 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
; Preferences DeployCenter
|
||||||
|
; Le fichier est lu au démarrage et n'est jamais modifié automatiquement.
|
||||||
|
[General]
|
||||||
|
Theme=Light
|
||||||
|
|
||||||
|
[Sources]
|
||||||
|
Paths=%DEPLOY_VOLUME%
|
||||||
|
|
||||||
|
[UltraVNC]
|
||||||
|
; Laisser vide pour générer un mot de passe aléatoire à chaque lancement.
|
||||||
|
; Pour les tests, renseigner un mot de passe ici. Il sera encodé dans ultravnc.ini par createpassword64.exe.
|
||||||
|
Password=
|
||||||
+1817
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,179 @@
|
|||||||
|
|
||||||
|
# HardwareInfo.ps1
|
||||||
|
# DeployCenter - Inventaire matériel
|
||||||
|
# Compatible Windows PowerShell 5.1 / WinPE
|
||||||
|
|
||||||
|
function Get-HardwareInfo {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
|
||||||
|
$info = [ordered]@{
|
||||||
|
Manufacturer = "Inconnu"
|
||||||
|
Model = "Inconnu"
|
||||||
|
SerialNumber = "Inconnu"
|
||||||
|
Firmware = "Inconnu"
|
||||||
|
Architecture = $env:PROCESSOR_ARCHITECTURE
|
||||||
|
WinPE = $false
|
||||||
|
Disks = @()
|
||||||
|
Errors = @()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fabricant et modele du PC
|
||||||
|
try {
|
||||||
|
$computer = Get-CimInstance `
|
||||||
|
-ClassName Win32_ComputerSystem `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
if ($computer.Manufacturer) {
|
||||||
|
$info.Manufacturer = $computer.Manufacturer.Trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($computer.Model) {
|
||||||
|
$info.Model = $computer.Model.Trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$info.Errors += "Modele du PC : $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Numero de serie
|
||||||
|
try {
|
||||||
|
$bios = Get-CimInstance `
|
||||||
|
-ClassName Win32_BIOS `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
if ($bios.SerialNumber) {
|
||||||
|
$info.SerialNumber = $bios.SerialNumber.Trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$info.Errors += "Numero de serie : $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detection du firmware
|
||||||
|
try {
|
||||||
|
$firmwareKey = Get-ItemProperty `
|
||||||
|
-Path "HKLM:\SYSTEM\CurrentControlSet\Control" `
|
||||||
|
-Name PEFirmwareType `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
switch ([int]$firmwareKey.PEFirmwareType) {
|
||||||
|
1 { $info.Firmware = "BIOS (Legacy)" }
|
||||||
|
2 { $info.Firmware = "UEFI" }
|
||||||
|
default { $info.Firmware = "Indetermine" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$info.Errors += "Mode firmware indisponible"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detection de WinPE
|
||||||
|
$info.WinPE = Test-Path `
|
||||||
|
"HKLM:\SYSTEM\CurrentControlSet\Control\MiniNT"
|
||||||
|
|
||||||
|
# Inventaire des disques physiques
|
||||||
|
try {
|
||||||
|
$disks = Get-CimInstance `
|
||||||
|
-ClassName Win32_DiskDrive `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
foreach ($disk in $disks) {
|
||||||
|
$info.Disks += [PSCustomObject]@{
|
||||||
|
Index = [int]$disk.Index
|
||||||
|
Model = $disk.Model
|
||||||
|
SizeBytes = [long]$disk.Size
|
||||||
|
SizeGB = [math]::Round(
|
||||||
|
([double]$disk.Size / 1GB), 1
|
||||||
|
)
|
||||||
|
InterfaceType = $disk.InterfaceType
|
||||||
|
Status = $disk.Status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$info.Errors += "Inventaire des disques : $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Retourner un objet exploitable par les autres modules
|
||||||
|
[PSCustomObject]$info
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Execution du test
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
$LogDirectory = "X:\DeployLogs"
|
||||||
|
$JsonPath = Join-Path $LogDirectory "hardware.json"
|
||||||
|
$TextPath = Join-Path $LogDirectory "hardware.log"
|
||||||
|
|
||||||
|
try {
|
||||||
|
New-Item -ItemType Directory `
|
||||||
|
-Path $LogDirectory `
|
||||||
|
-Force `
|
||||||
|
-ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
throw "Impossible de creer le repertoire de logs : $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = Get-HardwareInfo
|
||||||
|
|
||||||
|
# Export JSON en UTF-8 avec BOM sous Windows PowerShell 5.1
|
||||||
|
try {
|
||||||
|
$json = $result | ConvertTo-Json -Depth 5
|
||||||
|
|
||||||
|
Set-Content `
|
||||||
|
-Path $JsonPath `
|
||||||
|
-Value $json `
|
||||||
|
-Encoding UTF8 `
|
||||||
|
-ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
throw "Impossible d'enregistrer le JSON : $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Construire un journal texte sans utiliser Write-Host
|
||||||
|
$lines = @(
|
||||||
|
"=== DeployCenter - Inventaire materiel ==="
|
||||||
|
"Date : $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||||
|
"Fabricant : $($result.Manufacturer)"
|
||||||
|
"Modele : $($result.Model)"
|
||||||
|
"Numero serie : $($result.SerialNumber)"
|
||||||
|
"Firmware : $($result.Firmware)"
|
||||||
|
"Architecture : $($result.Architecture)"
|
||||||
|
"WinPE : $($result.WinPE)"
|
||||||
|
""
|
||||||
|
"=== Disques detectes ==="
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($disk in $result.Disks) {
|
||||||
|
$lines += (
|
||||||
|
"Disque {0} : {1} - {2} Go - {3} - {4}" -f
|
||||||
|
$disk.Index,
|
||||||
|
$disk.Model,
|
||||||
|
$disk.SizeGB,
|
||||||
|
$disk.InterfaceType,
|
||||||
|
$disk.Status
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result.Errors.Count -gt 0) {
|
||||||
|
$lines += ""
|
||||||
|
$lines += "=== Erreurs ==="
|
||||||
|
|
||||||
|
foreach ($message in $result.Errors) {
|
||||||
|
$lines += $message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
||||||
|
|
||||||
|
[System.IO.File]::WriteAllLines(
|
||||||
|
$TextPath,
|
||||||
|
[string[]]$lines,
|
||||||
|
$utf8
|
||||||
|
)
|
||||||
|
|
||||||
|
# Renvoyer l'objet comme sortie principale du script
|
||||||
|
$result
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# DeployCenter - Gestion des journaux
|
||||||
|
# Compatible Windows PowerShell 5.1
|
||||||
|
|
||||||
|
function Initialize-DeployCenterLogging {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$LogRoot,
|
||||||
|
|
||||||
|
[AllowNull()]
|
||||||
|
[string]$IPv4 = $null
|
||||||
|
)
|
||||||
|
|
||||||
|
$script:LogRoot = $LogRoot
|
||||||
|
if (-not (Test-Path -LiteralPath $script:LogRoot -PathType Container)) {
|
||||||
|
New-Item -Path $script:LogRoot -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# L'adresse IPv4 est fournie par Modules\\Network.psm1.
|
||||||
|
$script:LogIPv4 = $IPv4
|
||||||
|
|
||||||
|
$logIpTag = if ([string]::IsNullOrWhiteSpace($script:LogIPv4)) {
|
||||||
|
'sans-ip'
|
||||||
|
} else {
|
||||||
|
$script:LogIPv4 -replace '[^0-9A-Za-z.-]', '_'
|
||||||
|
}
|
||||||
|
$script:LogFile = Join-Path $script:LogRoot (
|
||||||
|
'DeployCenter_{0}_{1}.log' -f $logIpTag, (Get-Date -Format 'yyyyMMdd_HHmmss')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Message,
|
||||||
|
|
||||||
|
[ValidateSet('INFO', 'SUCCESS', 'WARNING', 'ERROR')]
|
||||||
|
[string]$Level = 'INFO'
|
||||||
|
)
|
||||||
|
|
||||||
|
$line = '{0} [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
|
||||||
|
|
||||||
|
try {
|
||||||
|
[System.IO.File]::AppendAllText(
|
||||||
|
$script:LogFile,
|
||||||
|
$line + [Environment]::NewLine,
|
||||||
|
[System.Text.Encoding]::UTF8
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
# Le journal graphique reste disponible si le fichier n'est pas accessible.
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -ne $script:txtLog -and -not $script:txtLog.IsDisposed) {
|
||||||
|
$script:txtLog.AppendText($line + [Environment]::NewLine)
|
||||||
|
$script:txtLog.SelectionStart = $script:txtLog.TextLength
|
||||||
|
$script:txtLog.ScrollToCaret()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# DeployCenter - Détection réseau
|
||||||
|
# Compatible Windows PowerShell 5.1
|
||||||
|
|
||||||
|
function Get-DeployNetworkIPv4 {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
|
||||||
|
# Priorité à une interface avec passerelle par défaut ; exclut loopback,
|
||||||
|
# APIPA et adresses IPv4 non routables couramment inutiles en WinPE.
|
||||||
|
$candidates = @()
|
||||||
|
try {
|
||||||
|
foreach ($cfg in (Get-CimInstance Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=True' -ErrorAction Stop)) {
|
||||||
|
$hasGateway = @($cfg.DefaultIPGateway | Where-Object { $_ -match '^\d{1,3}(\.\d{1,3}){3}$' }).Count -gt 0
|
||||||
|
foreach ($address in @($cfg.IPAddress | Where-Object { $_ -match '^\d{1,3}(\.\d{1,3}){3}$' })) {
|
||||||
|
if ($address -eq '127.0.0.1' -or $address -like '169.254.*' -or $address -eq '0.0.0.0') { continue }
|
||||||
|
$candidates += [pscustomobject]@{ Address = $address; HasGateway = $hasGateway; Index = [int]$cfg.Index }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# La détection réseau ne doit pas empêcher le démarrage de DeployCenter.
|
||||||
|
if (Get-Command Write-Log -ErrorAction SilentlyContinue) {
|
||||||
|
Write-Log "Détection réseau WMI : $($_.Exception.Message)" 'WARNING'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$chosen = $candidates | Sort-Object @{Expression='HasGateway';Descending=$true}, Index | Select-Object -First 1
|
||||||
|
if ($chosen) { return [string]$chosen.Address }
|
||||||
|
return $null
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
Set-StrictMode -Version 2.0
|
||||||
|
|
||||||
|
function ConvertFrom-ThemeColor {
|
||||||
|
param([Parameter(Mandatory=$true)][string]$Value)
|
||||||
|
$hex = $Value.Trim().TrimStart('#')
|
||||||
|
if ($hex -notmatch '^[0-9A-Fa-f]{6}$') {
|
||||||
|
throw "Couleur invalide dans un thème : '$Value'. Utiliser #RRGGBB."
|
||||||
|
}
|
||||||
|
return [System.Drawing.Color]::FromArgb(
|
||||||
|
[Convert]::ToInt32($hex.Substring(0,2),16),
|
||||||
|
[Convert]::ToInt32($hex.Substring(2,2),16),
|
||||||
|
[Convert]::ToInt32($hex.Substring(4,2),16)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-DeployCenterThemeNames {
|
||||||
|
param([Parameter(Mandatory=$true)][string]$ThemesRoot)
|
||||||
|
$names = @()
|
||||||
|
foreach ($file in (Get-ChildItem -LiteralPath $ThemesRoot -Filter '*.psd1' -File -ErrorAction SilentlyContinue)) {
|
||||||
|
if ($file.BaseName -match '^[A-Za-z0-9_-]+$') { $names += $file.BaseName }
|
||||||
|
}
|
||||||
|
return @($names | Sort-Object -Unique)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Import-DeployCenterTheme {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$ThemesRoot,
|
||||||
|
[Parameter(Mandatory=$true)][string]$ThemeName
|
||||||
|
)
|
||||||
|
if ($ThemeName -notmatch '^[A-Za-z0-9_-]+$') { throw "Nom de thème invalide : $ThemeName" }
|
||||||
|
$path = Join-Path $ThemesRoot ($ThemeName + '.psd1')
|
||||||
|
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Thème introuvable : $ThemeName" }
|
||||||
|
$data = Import-PowerShellDataFile -LiteralPath $path
|
||||||
|
$required = @('Background','Panel','PanelAlt','Text','Secondary','Accent','Success','Border','Danger')
|
||||||
|
foreach ($key in $required) {
|
||||||
|
if (-not $data.ContainsKey($key)) { throw "Le thème '$ThemeName' ne contient pas la clé '$key'." }
|
||||||
|
}
|
||||||
|
$theme = @{}
|
||||||
|
foreach ($key in $required) { $theme[$key] = ConvertFrom-ThemeColor -Value ([string]$data[$key]) }
|
||||||
|
foreach ($key in @('TableHeader','TableRowEven','TableRowOdd')) {
|
||||||
|
if ($data.ContainsKey($key)) { $theme[$key] = ConvertFrom-ThemeColor -Value ([string]$data[$key]) }
|
||||||
|
else { $theme[$key] = $theme.PanelAlt }
|
||||||
|
}
|
||||||
|
$buttonDefaults = @{
|
||||||
|
Button = @{ Background=$theme.PanelAlt; Foreground=$theme.Text; Border=$theme.Border; HoverBackground=$theme.Panel; PressedBackground=$theme.Border; DisabledBackground=$theme.Panel; DisabledForeground=$theme.Secondary }
|
||||||
|
PrimaryButton = @{ Background=$theme.Accent; Foreground=[System.Drawing.Color]::White; Border=$theme.Accent; HoverBackground=$theme.Accent; PressedBackground=$theme.Accent; DisabledBackground=$theme.PanelAlt; DisabledForeground=$theme.Secondary }
|
||||||
|
DangerButton = @{ Background=$theme.Danger; Foreground=[System.Drawing.Color]::White; Border=$theme.Danger; HoverBackground=$theme.Danger; PressedBackground=$theme.Danger; DisabledBackground=$theme.PanelAlt; DisabledForeground=$theme.Secondary }
|
||||||
|
}
|
||||||
|
foreach ($role in @('Button','PrimaryButton','DangerButton')) {
|
||||||
|
$theme[$role] = @{}
|
||||||
|
$provided = $data[$role]
|
||||||
|
foreach ($property in @('Background','Foreground','Border','HoverBackground','PressedBackground','DisabledBackground','DisabledForeground')) {
|
||||||
|
$raw = $null
|
||||||
|
if ($provided -is [System.Collections.IDictionary] -and $provided.Contains($property)) { $raw = [string]$provided[$property] }
|
||||||
|
if ([string]::IsNullOrWhiteSpace($raw)) { $theme[$role][$property] = $buttonDefaults[$role][$property] }
|
||||||
|
else { $theme[$role][$property] = ConvertFrom-ThemeColor -Value $raw }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $theme
|
||||||
|
}
|
||||||
|
|
||||||
|
Export-ModuleMember -Function ConvertFrom-ThemeColor, Get-DeployCenterThemeNames, Import-DeployCenterTheme
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
function Set-DeployCenterButtonStyle {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][System.Windows.Forms.Button]$Button,
|
||||||
|
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Theme,
|
||||||
|
[ValidateSet('Button','PrimaryButton','DangerButton')][string]$Role = 'Button'
|
||||||
|
)
|
||||||
|
$style = $Theme[$Role]
|
||||||
|
$Button.BackColor = $style.Background
|
||||||
|
$Button.ForeColor = $style.Foreground
|
||||||
|
$Button.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
|
||||||
|
$Button.UseVisualStyleBackColor = $false
|
||||||
|
$Button.FlatAppearance.BorderSize = 1
|
||||||
|
$Button.FlatAppearance.BorderColor = $style.Border
|
||||||
|
$Button.FlatAppearance.MouseOverBackColor = $style.HoverBackground
|
||||||
|
$Button.FlatAppearance.MouseDownBackColor = $style.PressedBackground
|
||||||
|
if (-not $Button.Enabled) {
|
||||||
|
$Button.BackColor = $style.DisabledBackground
|
||||||
|
$Button.ForeColor = $style.DisabledForeground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Export-ModuleMember -Function Set-DeployCenterButtonStyle
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Module VNC de DeployCenter
|
||||||
|
# Exécuté uniquement avec -Vnc dans le scope principal pour partager le contexte WinPE.
|
||||||
|
|
||||||
|
function Start-DeployCenterVnc {
|
||||||
|
$parentRoot = Split-Path -Path $script:DeployRoot -Parent
|
||||||
|
$sourceVnc = Join-Path $parentRoot 'uvnc'
|
||||||
|
$sourceExe = Join-Path $sourceVnc 'winvnc.exe'
|
||||||
|
$passwordTool = Join-Path $sourceVnc 'createpassword64.exe'
|
||||||
|
$sourceIni = Join-Path $sourceVnc 'ultravnc.ini'
|
||||||
|
|
||||||
|
$script:DeployIPv4 = Get-DeployNetworkIPv4
|
||||||
|
if ([string]::IsNullOrWhiteSpace($script:DeployIPv4)) {
|
||||||
|
Write-Log 'Aucune adresse IPv4 de déploiement détectée.' 'WARNING'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $sourceExe -PathType Leaf)) {
|
||||||
|
Write-Log "UltraVNC non démarré : exécutable absent ($sourceExe)." 'WARNING'
|
||||||
|
Write-DeployInventory
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $passwordTool -PathType Leaf)) {
|
||||||
|
Write-Log "UltraVNC non démarré : createpassword64.exe absent ($passwordTool)." 'WARNING'
|
||||||
|
Write-DeployInventory
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $sourceIni -PathType Leaf)) {
|
||||||
|
Write-Log "UltraVNC non démarré : fichier absent ($sourceIni)." 'WARNING'
|
||||||
|
Write-DeployInventory
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Test-Path -LiteralPath $script:VncDirectory) {
|
||||||
|
Remove-Item -LiteralPath $script:VncDirectory -Recurse -Force -ErrorAction Stop
|
||||||
|
}
|
||||||
|
New-Item -Path $script:VncDirectory -ItemType Directory -Force | Out-Null
|
||||||
|
Copy-Item -Path (Join-Path $sourceVnc '*') -Destination $script:VncDirectory -Recurse -Force
|
||||||
|
# WinPE ne permet pas toujours d'ajouter des règles de pare-feu.
|
||||||
|
# La désactivation globale n'est effectuée que lorsque VNC est demandé.
|
||||||
|
$wpeutil = Join-Path $env:WINDIR 'System32\wpeutil.exe'
|
||||||
|
if (-not (Test-Path -LiteralPath $wpeutil -PathType Leaf)) {
|
||||||
|
$wpeutil = 'wpeutil.exe'
|
||||||
|
}
|
||||||
|
& $wpeutil DisableFirewall
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "wpeutil DisableFirewall a renvoyé le code $LASTEXITCODE."
|
||||||
|
}
|
||||||
|
Write-Log 'Pare-feu WinPE désactivé à la demande de -Vnc.' 'WARNING'
|
||||||
|
|
||||||
|
# Force le mode portable : l'INI actif est celui placé à côté de winvnc.exe.
|
||||||
|
$portableFlag = Join-Path $script:VncDirectory 'ultravnc.portable'
|
||||||
|
Set-Content -LiteralPath $portableFlag -Value '' -Encoding ASCII
|
||||||
|
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace([string]$script:Config.VncPassword)) {
|
||||||
|
$script:VncPassword = [string]$script:Config.VncPassword
|
||||||
|
$passwordSource = 'configuré dans DeployCenter.ini'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$script:VncPassword = -join (1..8 | ForEach-Object {
|
||||||
|
$alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'
|
||||||
|
$alphabet[(Get-Random -Minimum 0 -Maximum $alphabet.Length)]
|
||||||
|
})
|
||||||
|
$passwordSource = 'généré automatiquement'
|
||||||
|
}
|
||||||
|
|
||||||
|
# createpassword64 encode le mot de passe choisi dans le champ passwd d'ultravnc.ini.
|
||||||
|
$tool = Join-Path $script:VncDirectory 'createpassword64.exe'
|
||||||
|
$script:VncProcess = Start-Process -FilePath $tool -ArgumentList @($script:VncPassword) `
|
||||||
|
-WorkingDirectory $script:VncDirectory -Wait -PassThru -WindowStyle Hidden
|
||||||
|
if ($script:VncProcess.ExitCode -ne 0) {
|
||||||
|
throw "createpassword64.exe a renvoyé le code $($script:VncProcess.ExitCode)."
|
||||||
|
}
|
||||||
|
|
||||||
|
$activeIni = Join-Path $script:VncDirectory 'ultravnc.ini'
|
||||||
|
if (-not (Test-Path -LiteralPath $activeIni -PathType Leaf) -or
|
||||||
|
-not (Select-String -LiteralPath $activeIni -Pattern '^passwd\s*=' -Quiet)) {
|
||||||
|
throw "createpassword64.exe n'a pas créé le champ passwd dans ultravnc.ini."
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = Join-Path $script:VncDirectory 'winvnc.exe'
|
||||||
|
$script:VncProcess = Start-Process -FilePath $server -ArgumentList @('-run') `
|
||||||
|
-WorkingDirectory $script:VncDirectory -PassThru -WindowStyle Hidden
|
||||||
|
Start-Sleep -Milliseconds 800
|
||||||
|
if ($script:VncProcess.HasExited) {
|
||||||
|
throw "winvnc.exe s'est arrêté immédiatement (code $($script:VncProcess.ExitCode))."
|
||||||
|
}
|
||||||
|
$script:VncStarted = $true
|
||||||
|
Write-Log "UltraVNC démarré. IP : $($script:DeployIPv4) ; mot de passe $passwordSource." 'SUCCESS'
|
||||||
|
} catch {
|
||||||
|
Write-Log "Échec du démarrage UltraVNC : $($_.Exception.Message)" 'ERROR'
|
||||||
|
$script:VncStarted = $false
|
||||||
|
$script:VncPassword = $null
|
||||||
|
}
|
||||||
|
Write-DeployInventory
|
||||||
|
}
|
||||||
|
|
||||||
|
function Stop-DeployCenterVnc {
|
||||||
|
try {
|
||||||
|
if ($null -ne $script:VncProcess) {
|
||||||
|
$proc = Get-Process -Id $script:VncProcess.Id -ErrorAction SilentlyContinue
|
||||||
|
if ($null -ne $proc) { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue }
|
||||||
|
}
|
||||||
|
# Au cas où winvnc lance un processus serveur distinct, ne cibler que les
|
||||||
|
# processus dont le chemin correspond à notre copie temporaire.
|
||||||
|
foreach ($proc in (Get-CimInstance Win32_Process -Filter "Name='winvnc.exe'" -ErrorAction SilentlyContinue)) {
|
||||||
|
if ($proc.ExecutablePath -and $proc.ExecutablePath.StartsWith($script:VncDirectory, [StringComparison]::OrdinalIgnoreCase)) {
|
||||||
|
Stop-Process -Id $proc.ProcessId -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Log "Arrêt UltraVNC : $($_.Exception.Message)" 'WARNING'
|
||||||
|
}
|
||||||
|
# Le processus winvnc peut conserver brièvement un handle sur son dossier.
|
||||||
|
# Le dossier VNC est propre à ce processus PowerShell (uvnc_<PID>) : on ne
|
||||||
|
# supprime pas récursivement le dossier partagé InventoryDir, ce qui évite
|
||||||
|
# de bloquer le lancement suivant si un handle est encore ouvert.
|
||||||
|
try {
|
||||||
|
if (Test-Path -LiteralPath $script:InventoryFile -PathType Leaf) {
|
||||||
|
Remove-Item -LiteralPath $script:InventoryFile -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
if (Test-Path -LiteralPath $script:VncDirectory -PathType Container) {
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
Remove-Item -LiteralPath $script:VncDirectory -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# DeployCenter — première version de la réécriture
|
||||||
|
|
||||||
|
## Contenu
|
||||||
|
|
||||||
|
- `DeployCenter.ps1` : point d'entrée et logique applicative existante.
|
||||||
|
- `Modules/ThemeManager.psm1` : découverte, chargement et validation des thèmes PSD1, avec prise en charge des rôles de boutons.
|
||||||
|
- `Modules/UI.psm1` : style centralisé des boutons (normal, survol, appui et désactivé).
|
||||||
|
- `Themes/Light.psd1` et `Themes/Dark.psd1` : palettes et styles des boutons.
|
||||||
|
|
||||||
|
## Lancement
|
||||||
|
|
||||||
|
Depuis le dossier DeployCenter, lancer :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -ExecutionPolicy Bypass -File .\DeployCenter.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour lancer en fenêtre redimensionnable :
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -ExecutionPolicy Bypass -File .\DeployCenter.ps1 -Windowed
|
||||||
|
```
|
||||||
|
|
||||||
|
Conserver à côté du script les ressources habituelles du projet (`Assets`, `uvnc`, `HardwareInfo.ps1`, etc.). Les dossiers `Modules` et `Themes` doivent rester à côté de `DeployCenter.ps1`.
|
||||||
|
|
||||||
|
## Mise en page
|
||||||
|
|
||||||
|
Le redimensionnement de la fenêtre recalcule désormais les largeurs des panneaux, la taille du tableau et du journal, ainsi que la position des boutons. Les marges et hauteurs des contrôles restent volontairement fixes pour préserver la lisibilité sous WinPE.
|
||||||
|
|
||||||
|
## Limites connues de cette première étape
|
||||||
|
|
||||||
|
Cette version centralise le chargement des thèmes et le style des boutons, et introduit une mise en page adaptative. La logique de déploiement, la détection des sources, l'inventaire et UltraVNC restent pour l'instant dans le script principal : leur extraction en modules indépendants doit être faite progressivement afin de ne pas casser les dépendances PowerShell ni le fonctionnement WinPE.
|
||||||
|
|
||||||
|
Le script n'a pas pu être exécuté dans cet environnement, faute de moteur Windows PowerShell/WinForms. Il doit être validé sur la machine WinPE cible avant remplacement de la version actuelle.
|
||||||
|
|
||||||
|
|
||||||
|
## Mot de passe UltraVNC de test
|
||||||
|
|
||||||
|
Dans `DeployCenter.ini`, ajoutez la section suivante. Un champ `Password` non vide est utilisé tel quel au lieu de générer un mot de passe aléatoire ; `createpassword64.exe` continue à le convertir dans le format attendu par UltraVNC. Laissez `Password=` vide pour conserver le comportement par défaut.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[UltraVNC]
|
||||||
|
Password=MonMotDePasseDeTest
|
||||||
|
```
|
||||||
|
|
||||||
|
Le mot de passe est stocké en clair dans `DeployCenter.ini` : utilisez cette option uniquement dans un environnement de test protégé.
|
||||||
|
|
||||||
|
|
||||||
|
### Correctifs v3
|
||||||
|
- En-têtes du tableau des éditions Windows avec une couleur dédiée définie par le thème (`TableHeader`).
|
||||||
|
- Sélection unique explicite dans la liste des éditions et dessin de la sélection basé sur l'élément réellement sélectionné.
|
||||||
|
|
||||||
|
### Correctif v12 — pied de page
|
||||||
|
- La fenêtre principale affiche l'adresse IP de déploiement et le mot de passe UltraVNC dans son pied de page.
|
||||||
|
- Le pied de page commun reste activé par défaut ; le paramètre `-ShowFooter $false` permet de le masquer dans la fenêtre « Éditions Windows ». Les autres fenêtres conservent leur comportement habituel.
|
||||||
|
|
||||||
|
### Correctif v15 — pied de page cohérent
|
||||||
|
- Le pied de page commun affiche par défaut l’adresse IP et le mot de passe VNC dans toutes les fenêtres qui l’utilisent.
|
||||||
|
- Seule la fenêtre de sélection « Éditions Windows » masque entièrement le pied de page via `-ShowFooter $false`.
|
||||||
|
|
||||||
|
|
||||||
|
### Alternance des lignes des tableaux
|
||||||
|
|
||||||
|
Les tableaux utilisent une alternance paire/impaire définie dans chaque thème (`TableRowEven` et `TableRowOdd`). La sélection conserve la couleur d’accent du thème. Les valeurs sont partagées par les fenêtres Sources Windows et Éditions Windows.
|
||||||
|
|
||||||
|
|
||||||
|
## Paramètre de démarrage VNC (v18)
|
||||||
|
|
||||||
|
- Sans paramètre : UltraVNC ne démarre pas.
|
||||||
|
- Avec `-vnc` : UltraVNC démarre.
|
||||||
|
|
||||||
|
Exemple : `powershell.exe -ExecutionPolicy Bypass -File .\DeployCenter.ps1 -vnc`
|
||||||
|
|
||||||
|
|
||||||
|
## Options de lancement
|
||||||
|
|
||||||
|
- Sans paramètre : UltraVNC n'est pas démarré.
|
||||||
|
- Avec `-Vnc` (alias PowerShell utilisable sous la forme `-vnc`) : UltraVNC est démarré.
|
||||||
|
- Les fenêtres sont non redimensionnables par défaut via `Set-DeployCenterFormSizing`; passer `-Resizable` à cette fonction commune pour autoriser le redimensionnement.
|
||||||
|
|
||||||
|
|
||||||
|
## Module VNC
|
||||||
|
|
||||||
|
La gestion d'UltraVNC est isolée dans `Modules/VNC.psm1`. Le fichier est chargé par dot-sourcing uniquement lorsque `-Vnc` est fourni, afin de conserver le contexte partagé de DeployCenter.
|
||||||
|
|
||||||
|
- Sans `-Vnc`, UltraVNC ne démarre pas et DeployCenter ne demande pas la désactivation du pare-feu.
|
||||||
|
- Avec `-Vnc`, le module VNC exécute `wpeutil DisableFirewall` avant de lancer UltraVNC. Cette commande désactive le pare-feu WinPE dans son ensemble ; utilisez cette option sur un réseau de déploiement maîtrisé.
|
||||||
|
- Le démarrage VNC et les erreurs sont consignés dans le journal DeployCenter.
|
||||||
|
|
||||||
|
|
||||||
|
## Modules
|
||||||
|
|
||||||
|
- `Modules/Network.psm1` : détection de l’adresse IPv4 de déploiement, utilisée par le pied de page et le nom du journal.
|
||||||
|
- `Modules/Logging.psm1` : initialisation et écriture des journaux. L’adresse IPv4 détectée lui est transmise par le script principal.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
@{
|
||||||
|
Background = '#171A21'
|
||||||
|
Panel = '#222731'
|
||||||
|
PanelAlt = '#1C2029'
|
||||||
|
TableHeader = '#303744'
|
||||||
|
TableRowEven = '#303640'
|
||||||
|
TableRowOdd = '#414956'
|
||||||
|
Text = '#F1F5F9'
|
||||||
|
Secondary = '#9CA9BC'
|
||||||
|
Accent = '#3B82F6'
|
||||||
|
Success = '#4ADE80'
|
||||||
|
Border = '#414A59'
|
||||||
|
Danger = '#EF4444'
|
||||||
|
Button = @{
|
||||||
|
Background = '#303744'; Foreground = '#F1F5F9'; Border = '#414A59'
|
||||||
|
HoverBackground = '#3B4555'; PressedBackground = '#485365'
|
||||||
|
DisabledBackground = '#292E38'; DisabledForeground = '#7F8999'
|
||||||
|
}
|
||||||
|
PrimaryButton = @{
|
||||||
|
Background = '#3B82F6'; Foreground = '#FFFFFF'; Border = '#3B82F6'
|
||||||
|
HoverBackground = '#2563EB'; PressedBackground = '#1D4ED8'
|
||||||
|
DisabledBackground = '#344A6A'; DisabledForeground = '#9AAAC1'
|
||||||
|
}
|
||||||
|
DangerButton = @{
|
||||||
|
Background = '#EF4444'; Foreground = '#FFFFFF'; Border = '#EF4444'
|
||||||
|
HoverBackground = '#DC2626'; PressedBackground = '#B91C1C'
|
||||||
|
DisabledBackground = '#59383B'; DisabledForeground = '#BBA0A2'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
@{
|
||||||
|
Background = '#D1D5DB'
|
||||||
|
Panel = '#E5E7EB'
|
||||||
|
PanelAlt = '#F1F3F5'
|
||||||
|
TableHeader = '#D1D5DB'
|
||||||
|
TableRowEven = '#E5E7EB'
|
||||||
|
TableRowOdd = '#C9CED6'
|
||||||
|
Text = '#1E293B'
|
||||||
|
Secondary = '#475569'
|
||||||
|
Accent = '#2563EB'
|
||||||
|
Success = '#16A34A'
|
||||||
|
Border = '#AEB6C2'
|
||||||
|
Danger = '#DC2626'
|
||||||
|
Button = @{
|
||||||
|
Background = '#E5E7EB'; Foreground = '#1E293B'; Border = '#AEB6C2'
|
||||||
|
HoverBackground = '#D1D5DB'; PressedBackground = '#C2C8D0'
|
||||||
|
DisabledBackground = '#E5E7EB'; DisabledForeground = '#7B8492'
|
||||||
|
}
|
||||||
|
PrimaryButton = @{
|
||||||
|
Background = '#2563EB'; Foreground = '#FFFFFF'; Border = '#2563EB'
|
||||||
|
HoverBackground = '#1D4ED8'; PressedBackground = '#1E40AF'
|
||||||
|
DisabledBackground = '#C7D8F8'; DisabledForeground = '#F8FAFC'
|
||||||
|
}
|
||||||
|
DangerButton = @{
|
||||||
|
Background = '#DC2626'; Foreground = '#FFFFFF'; Border = '#DC2626'
|
||||||
|
HoverBackground = '#B91C1C'; PressedBackground = '#991B1B'
|
||||||
|
DisabledBackground = '#F2C5C5'; DisabledForeground = '#FFF7F7'
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user