DeployCenter
This commit is contained in:
commit
7c724d475e
14 files changed
+2460
No files matched your search
@@ -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 { }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user