Files
Omika/infra/deploy/Omika.Deploy.Common.psm1
T
2026-10-23 18:46:50 +02:00

739 lines
27 KiB
PowerShell

# ============================================================
# Omika - Module commun de deploiement des services Windows
# ============================================================
# Utilise par les scripts Install-Omika*.ps1 (UN FICHIER PAR DEPLOIEMENT).
#
# Ce module regroupe ce qui etait duplique ou implicite dans l'ancien
# Install-OmikaServices.ps1 monolithique :
# - controle des droits administrateur ;
# - messages homogenes (etape / info / ok / avertissement / echec) ;
# - creation de dossiers idempotente ;
# - ACL NTFS AVEC CONTROLE DU CODE RETOUR d'icacls ;
# - validation du compte de service (existence + droit « Ouvrir une session
# en tant que service ») ;
# - creation / suppression / verification d'un service Windows.
#
# COMPATIBILITE : Windows PowerShell 5.1 (aucune syntaxe PS 7).
#
# CE MODULE NE DEMARRE JAMAIS UN SERVICE. Il cree et configure uniquement ;
# le demarrage reste une action manuelle et explicite (doctrine Phase 5C.2C).
#
# A PROPOS DE -WhatIf :
# Les variables de preference ($WhatIfPreference) NE se propagent PAS dans
# l'etat de session d'un module. Les scripts appelants doivent donc transmettre
# explicitement -WhatIf:$Simulation a chaque appel. Chaque fonction marquee
# [CmdletBinding(SupportsShouldProcess)] l'accepte.
# ============================================================
# --- Messages ----------------------------------------------------------------
function Write-OmikaStep {
param([Parameter(Mandatory = $true)][string]$Message)
Write-Host ''
Write-Host "== $Message ==" -ForegroundColor Cyan
}
function Write-OmikaInfo {
param([Parameter(Mandatory = $true)][string]$Message)
Write-Host " .. $Message"
}
function Write-OmikaOk {
param([Parameter(Mandatory = $true)][string]$Message)
Write-Host " OK $Message" -ForegroundColor Green
}
function Write-OmikaWarn {
param([Parameter(Mandatory = $true)][string]$Message)
Write-Host " ATTENTION $Message" -ForegroundColor Yellow
}
function Write-OmikaFail {
param([Parameter(Mandatory = $true)][string]$Message)
Write-Host " ECHEC $Message" -ForegroundColor Red
}
# --- Prerequis ---------------------------------------------------------------
function Assert-OmikaAdmin {
<#
.SYNOPSIS
Verifie que la session PowerShell est elevee. Leve une exception sinon.
#>
$identite = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identite)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "Ce script doit etre execute en PowerShell ADMINISTRATEUR (clic droit -> Executer en tant qu'administrateur)."
}
}
function Get-OmikaPaths {
<#
.SYNOPSIS
Resout l'arborescence serveur C:\Omika (meme convention que
Install-OmikaServerFiles.ps1).
#>
param([string]$InstallRoot = 'C:\Omika')
return [pscustomobject]@{
InstallRoot = $InstallRoot
Apps = Join-Path $InstallRoot 'Apps'
Data = Join-Path $InstallRoot 'Data'
Logs = Join-Path $InstallRoot 'Logs'
Config = Join-Path $InstallRoot 'Config'
Backups = Join-Path $InstallRoot 'Backups'
Deploy = Join-Path $InstallRoot 'Deploy'
}
}
# --- Dossiers ----------------------------------------------------------------
function New-OmikaDirectory {
[CmdletBinding(SupportsShouldProcess = $true)]
param([Parameter(Mandatory = $true)][string]$Path)
if (Test-Path -LiteralPath $Path) {
Write-OmikaInfo "Dossier deja present : $Path"
return $false
}
if ($PSCmdlet.ShouldProcess($Path, 'Creer le dossier')) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
Write-OmikaOk "Dossier cree : $Path"
return $true
}
return $false
}
# --- Compte de service -------------------------------------------------------
function Resolve-OmikaAccountName {
<#
.SYNOPSIS
Normalise un compte de service en forme NON AMBIGUE 'ORDINATEUR\compte'.
.DESCRIPTION
L'ancien script passait la forme '.\svc_omika_api' directement a icacls.
La forme '.\' est une commodite de ligne de commande : elle n'est pas la
forme documentee d'icacls (qui attend 'Compte' ou 'Domaine\Compte').
Comme le bac a sable de developpement ne permet pas de verifier les
ecritures d'ACL, ce module ne pariera pas sur la forme '.\' : il resout
systematiquement en 'ORDINATEUR\compte', forme non ambigue, et le script
CONTROLE le code retour d'icacls (voir Grant-OmikaAcl).
#>
param(
[Parameter(Mandatory = $true)][string]$Account,
[string]$ComputerName = $env:COMPUTERNAME
)
if ($Account -eq '.') { return "$ComputerName\$env:USERNAME" }
if ($Account.StartsWith('.\')) { return ('{0}\{1}' -f $ComputerName, $Account.Substring(2)) }
if ($Account.StartsWith('./')) { return ('{0}\{1}' -f $ComputerName, $Account.Substring(2)) }
return $Account
}
function Test-OmikaLocalAccount {
<#
.SYNOPSIS
Verifie qu'un compte de service existe sur la machine.
.OUTPUTS
PSCustomObject : Nom, FormeResolue, Existe ($true/$false/$null si domaine),
Actif, Sid, Raison.
#>
param(
[Parameter(Mandatory = $true)][string]$Account,
[string]$ComputerName = $env:COMPUTERNAME
)
$resolu = Resolve-OmikaAccountName -Account $Account -ComputerName $ComputerName
$resultat = [pscustomobject]@{
Nom = $Account
FormeResolue = $resolu
Existe = $null
Actif = $null
Sid = $null
Raison = ''
}
# Compte de domaine : non verifiable localement (le DC est autoritaire).
$nomCourt = $resolu
if ($resolu.Contains('\')) { $nomCourt = $resolu.Substring($resolu.IndexOf('\') + 1) }
if ($resolu.Contains('\') -and -not $resolu.StartsWith("$ComputerName\", [System.StringComparison]::OrdinalIgnoreCase)) {
$resultat.Raison = 'compte de domaine : existence non verifiable depuis ce serveur'
return $resultat
}
# 1) Get-LocalUser (module LocalAccounts) : leve UserNotFoundException si le
# compte est absent. C'est le chemin nominal.
if (Get-Command -Name Get-LocalUser -ErrorAction SilentlyContinue) {
try {
$utilisateur = Get-LocalUser -Name $nomCourt -ErrorAction Stop
$resultat.Existe = $true
$resultat.Actif = [bool]$utilisateur.Enabled
if ($utilisateur.SID) { $resultat.Sid = $utilisateur.SID.Value }
return $resultat
}
catch {
if ($_.Exception.GetType().Name -eq 'UserNotFoundException') {
$resultat.Existe = $false
$resultat.Raison = "compte local introuvable : $resolu"
return $resultat
}
# Autre erreur (droits, module...) : on ne conclut pas, on passe au repli.
}
}
# 2) Repli net.exe user : code retour 0 = le compte existe, non nul = absent.
#
# PAS DE REPLI ADSI. Verifie en test sur ce poste :
# [ADSI]("WinNT://ORDINATEUR/compte,user") -> AUCUNE exception pour un
# compte inexistant (et .Name vide) : ce repli produisait un FAUX POSITIF
# « Existe = True », c'est-a-dire exactement le diagnostic inverse de
# celui attendu. Un faux positif est pire qu'une incertitude ici : le
# script laisserait creer un service avec un compte inexistant.
# Le nom est filtre sur le guillemet simple (argument de ligne de commande).
if ($nomCourt -notmatch "'") {
$ancien = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$null = (& net.exe user $nomCourt 2>&1 | Out-String)
$code = $LASTEXITCODE
}
finally {
$ErrorActionPreference = $ancien
}
if ($code -eq 0) {
$resultat.Existe = $true
return $resultat
}
$resultat.Existe = $false
$resultat.Raison = "compte local introuvable : $resolu"
return $resultat
}
# 3) Ni l'un ni l'autre exploitable : on reste INDETERMINE (jamais « existe »).
$resultat.Raison = 'existence non verifiable (ni Get-LocalUser ni net.exe exploitables)'
return $resultat
}
function Get-OmikaServiceLogonRight {
<#
.SYNOPSIS
Lit la liste des comptes titulaires du droit « Ouvrir une session en tant
que service » (SeServiceLogonRight) via un export secedit.
.OUTPUTS
Tableau d'entrees (SID ou nom), ou $null si indeterminable (non admin).
#>
$fichier = Join-Path $env:TEMP ('omika-secedit-{0}.inf' -f ([guid]::NewGuid().ToString('N')))
$ancien = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
& secedit.exe /export /areas USER_RIGHTS /cfg $fichier | Out-Null
if (-not (Test-Path -LiteralPath $fichier)) { return $null }
# Les noms de privileges (SeServiceLogonRight) ne sont PAS localises :
# le filtrage fonctionne sur un Windows francais.
$ligne = Get-Content -LiteralPath $fichier |
Where-Object { $_ -match '^\s*SeServiceLogonRight\s*=' } |
Select-Object -First 1
if (-not $ligne) { return @() }
$valeur = ($ligne -split '=', 2)[1]
$entrees = @($valeur -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
return $entrees
}
catch {
return $null
}
finally {
$ErrorActionPreference = $ancien
if (Test-Path -LiteralPath $fichier) {
Remove-Item -LiteralPath $fichier -Force -ErrorAction SilentlyContinue
}
}
}
function Test-OmikaServiceLogonRight {
<#
.SYNOPSIS
Verifie que le compte possede le droit « Ouvrir une session en tant que
service ». C'est la cause n1 d'echec de demarrage (erreur 1069/1053).
.OUTPUTS
$true, $false, ou $null si le droit n'a pas pu etre determine.
#>
param(
[Parameter(Mandatory = $true)][string]$Account,
[string]$ComputerName = $env:COMPUTERNAME
)
$compte = Test-OmikaLocalAccount -Account $Account -ComputerName $ComputerName
$droits = Get-OmikaServiceLogonRight
if ($null -eq $droits) { return $null }
$candidats = @()
if ($compte.Sid) { $candidats += $compte.Sid }
if ($compte.FormeResolue) { $candidats += $compte.FormeResolue }
$nomCourt = $compte.FormeResolue
if ($nomCourt.Contains('\')) { $nomCourt = $nomCourt.Substring($nomCourt.IndexOf('\') + 1) }
if ($nomCourt) { $candidats += $nomCourt }
foreach ($entree in $droits) {
foreach ($candidat in $candidats) {
if ([string]::IsNullOrWhiteSpace($candidat)) { continue }
if ($entree -eq $candidat) { return $true }
# secedit exporte les comptes locaux sous forme de SID (*S-1-5-21-...).
if ($entree -like "*$candidat*") { return $true }
}
}
return $false
}
function Assert-OmikaServiceAccount {
<#
.SYNOPSIS
Verifie le compte de service avant la creation (existence + droit de
connexion). Leve une exception si le compte n'existe pas ; avertit
seulement si un droit manque (New-Service peut encore reussir, mais le
DEMARRAGE echouera).
#>
param(
[Parameter(Mandatory = $true)][string]$Account,
[string]$ComputerName = $env:COMPUTERNAME
)
$compte = Test-OmikaLocalAccount -Account $Account -ComputerName $ComputerName
Write-OmikaInfo "Compte de service : $($compte.FormeResolue)"
if ($compte.Existe -eq $false) {
throw ("Compte de service introuvable : $($compte.FormeResolue). " +
"Creez le compte local dedie AVANT ce script (jamais LocalSystem, " +
"jamais un compte humain).")
}
if ($compte.Existe -eq $null) {
Write-OmikaWarn "Existence du compte non verifiable ($($compte.Raison))."
}
elseif ($compte.Actif -eq $false) {
Write-OmikaWarn "Le compte $($compte.FormeResolue) est DESACTIVE : le service ne demarrera pas."
}
$droit = Test-OmikaServiceLogonRight -Account $Account -ComputerName $ComputerName
if ($droit -eq $false) {
Write-OmikaWarn "Le compte $($compte.FormeResolue) ne possede PAS le droit"
Write-OmikaWarn " « Ouvrir une session en tant que service » (SeServiceLogonRight)."
Write-OmikaWarn " -> La creation du service reussira, mais le DEMARRAGE echouera (erreur 1069)."
Write-OmikaWarn " -> secpol.msc : Attribution des droits utilisateur / Ouvrir une session en tant que service."
}
elseif ($droit -eq $null) {
Write-OmikaWarn "Droit de connexion en tant que service : non verifiable (secedit inaccessible)."
}
else {
Write-OmikaOk "Droit « Ouvrir une session en tant que service » : present."
}
return $compte
}
# --- ACL ---------------------------------------------------------------------
function Grant-OmikaAcl {
<#
.SYNOPSIS
Pose une ACL NTFS et CONTROLE le code retour d'icacls.
.DESCRIPTION
L'ancien script ignorait totalement le code retour d'icacls : une ACL
refusee passait inapercue a l'installation et ne se manifestait qu'au
premier demarrage du service, par une erreur d'acces illisible.
Deux cas sont distingues :
- dossier absent -> avertissement BRUYANT (sauf -RequireExisting) ;
- icacls en echec -> exception (le script s'arrete).
.PARAMETER Right
M = Modifier (ecriture), R = Lecture seule.
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $true)][string]$Account,
[ValidateSet('M', 'R')][string]$Right = 'M',
[switch]$RequireExisting
)
$resolu = Resolve-OmikaAccountName -Account $Account
if (-not (Test-Path -LiteralPath $Path)) {
$message = "Dossier absent, aucune ACL posee : $Path (compte $resolu)"
if ($RequireExisting) { throw $message }
Write-OmikaWarn $message
Write-OmikaWarn ' -> le service subira une erreur d''acces a son premier demarrage.'
return $false
}
$ace = '{0}:(OI)(CI){1}' -f $resolu, $Right
if (-not $PSCmdlet.ShouldProcess($Path, "ACL $Right pour $resolu")) { return $false }
# icacls ecrit ses erreurs sur stderr : sous $ErrorActionPreference='Stop' une
# NativeCommandError deviendrait TERMINANTE et masquerait le vrai code retour.
# On isole donc l'appel.
$ancien = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$sortie = (& icacls.exe $Path /grant:r $ace 2>&1 | Out-String)
$code = $LASTEXITCODE
}
finally {
$ErrorActionPreference = $ancien
}
if ($code -ne 0) {
throw ("icacls a echoue (code $code) sur '$Path' pour '$resolu'.`n" +
"Sortie icacls : $($sortie.Trim())`n" +
"Verifiez que le compte existe et que la forme du nom est acceptee " +
"('ORDINATEUR\compte').")
}
Write-OmikaOk "ACL $Right : $resolu sur $Path"
return $true
}
# --- Service Windows ---------------------------------------------------------
function Get-OmikaServiceState {
<#
.SYNOPSIS
Etat reel d'un service (CIM), ou $null s'il n'existe pas.
Permet de detecter un service existant qui pointe sur un AUTRE binaire
ou tourne sous un AUTRE compte que ceux attendus.
#>
param([Parameter(Mandatory = $true)][string]$Name)
$service = Get-CimInstance -ClassName Win32_Service -Filter ("Name='{0}'" -f $Name) -ErrorAction SilentlyContinue
if (-not $service) { return $null }
return [pscustomobject]@{
Nom = $service.Name
NomAffiche = $service.DisplayName
Etat = $service.State
Demarrage = $service.StartMode
CheminExe = $service.PathName
Compte = $service.StartName
Description = $service.Description
}
}
function Install-OmikaService {
<#
.SYNOPSIS
Cree un service Windows (StartupType Manual par defaut), de facon
IDEMPOTENTE, et verifie ce qui a ete reellement cree.
.DESCRIPTION
- Service inexistant -> creation (mot de passe saisi via Get-Credential,
jamais en dur, jamais affiche).
- Service existant -> aucune creation ; comparaison du binaire et du
compte avec les valeurs attendues, avec avertissement si divergence.
- Ne demarre JAMAIS le service.
.OUTPUTS
PSCustomObject : Nom, Cree, ExistaitDeja, Conforme, Etat.
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory = $true)][string]$Name,
[Parameter(Mandatory = $true)][string]$DisplayName,
[Parameter(Mandatory = $true)][string]$ExePath,
[Parameter(Mandatory = $true)][string]$Account,
[string]$Description = '',
[ValidateSet('Manual', 'Automatic', 'Disabled')][string]$StartupType = 'Manual'
)
$resultat = [pscustomobject]@{
Nom = $Name
Cree = $false
ExistaitDeja = $false
Conforme = $null
Etat = ''
}
if (-not (Test-Path -LiteralPath $ExePath -PathType Leaf)) {
throw "Executable introuvable pour le service '$Name' : $ExePath"
}
$attendu = '"{0}"' -f $ExePath
$existant = Get-OmikaServiceState -Name $Name
if ($existant) {
$resultat.ExistaitDeja = $true
$resultat.Etat = $existant.Etat
Write-OmikaInfo "Service '$Name' deja present : aucune creation (idempotent)."
$conforme = $true
if ($existant.CheminExe -and $existant.CheminExe.Trim('"') -ne $ExePath) {
Write-OmikaWarn " Binaire DIFFERENT de celui attendu :"
Write-OmikaWarn " attendu : $ExePath"
Write-OmikaWarn " reel : $($existant.CheminExe)"
$conforme = $false
}
$compteAttendu = (Resolve-OmikaAccountName -Account $Account).ToLowerInvariant()
if ($existant.Compte -and $existant.Compte.ToLowerInvariant() -ne $compteAttendu) {
Write-OmikaWarn " Compte DIFFERENT de celui attendu :"
Write-OmikaWarn " attendu : $compteAttendu"
Write-OmikaWarn " reel : $($existant.Compte)"
$conforme = $false
}
$resultat.Conforme = $conforme
return $resultat
}
if ($PSCmdlet.ShouldProcess($Name, "Creer le service Windows (compte $Account, $StartupType)")) {
$cred = Get-Credential -UserName $Account -Message "Mot de passe du compte de service $Name"
$parametres = @{
Name = $Name
DisplayName = $DisplayName
BinaryPathName = $attendu
Credential = $cred
StartupType = $StartupType
}
if ($Description) { $parametres['Description'] = $Description }
New-Service @parametres | Out-Null
Write-OmikaOk "Service cree : $Name ($StartupType, non demarre)."
$resultat.Cree = $true
$etat = Get-OmikaServiceState -Name $Name
if ($etat) { $resultat.Etat = $etat.Etat }
}
return $resultat
}
function Remove-OmikaService {
<#
.SYNOPSIS
Supprime un service Windows (rollback). Refuse si le service tourne
encore : il faut l'arreter explicitement d'abord (aucun arret automatique).
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param([Parameter(Mandatory = $true)][string]$Name)
$existant = Get-OmikaServiceState -Name $Name
if (-not $existant) {
Write-OmikaInfo "Service '$Name' absent : rien a supprimer."
return $false
}
if ($existant.Etat -eq 'Running') {
throw "Le service '$Name' est en cours d'execution : arretez-le explicitement (Stop-Service $Name) avant de le supprimer."
}
if ($PSCmdlet.ShouldProcess($Name, 'Supprimer le service Windows')) {
$ancien = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$sortie = (& sc.exe delete $Name 2>&1 | Out-String)
$code = $LASTEXITCODE
}
finally {
$ErrorActionPreference = $ancien
}
if ($code -ne 0) { throw "sc.exe delete a echoue (code $code) pour '$Name' : $($sortie.Trim())" }
Write-OmikaOk "Service supprime : $Name"
return $true
}
return $false
}
# --- Restitutions ------------------------------------------------------------
function Write-OmikaServiceSummary {
<#
.SYNOPSIS
Affiche un recapitulatif de fin d'installation : etat reel du service
et rappels de ce qui reste MANUEL.
#>
param(
[Parameter(Mandatory = $true)][string]$Name,
[string]$ExePath = '',
[string]$Account = '',
[string[]]$Rappels = @(),
[switch]$Supprime
)
Write-Host ''
if ($Supprime) {
Write-Host ("{0} : SUPPRIME." -f $Name) -ForegroundColor Green
return
}
Write-Host ("{0} : INSTALLATION TERMINEE (service NON demarre)." -f $Name) -ForegroundColor Green
$etat = Get-OmikaServiceState -Name $Name
if ($etat) {
Write-Host ''
Write-Host ' Etat reel du service :'
Write-Host (" Nom : {0}" -f $etat.Nom)
Write-Host (" Nom affiche : {0}" -f $etat.NomAffiche)
Write-Host (" Etat : {0}" -f $etat.Etat)
Write-Host (" Demarrage : {0}" -f $etat.Demarrage)
Write-Host (" Binaire : {0}" -f $etat.CheminExe)
Write-Host (" Compte : {0}" -f $etat.Compte)
}
else {
Write-OmikaWarn "Le service '$Name' est introuvable apres installation (mode -WhatIf ?)."
}
if ($Rappels.Count -gt 0) {
Write-Host ''
Write-Host ' RAPPELS (jamais automatises par ce script) :' -ForegroundColor Yellow
foreach ($rappel in $Rappels) {
Write-Host (" - {0}" -f $rappel) -ForegroundColor Yellow
}
}
Write-Host ''
Write-Host ' Le demarrage est une action MANUELLE et explicite :' -ForegroundColor Yellow
Write-Host (" Start-Service {0}" -f $Name) -ForegroundColor Yellow
Write-Host ''
}
# --- Port loopback -----------------------------------------------------------
function Get-OmikaDebutPlageEphemere {
<#
.SYNOPSIS
Borne basse de la plage de ports ephemere Windows (49152 par defaut).
.DESCRIPTION
La sortie de netsh est LOCALISEE : on prend le premier nombre de la
reponse, qui est toujours le port de debut, quelle que soit la langue.
#>
$texte = (netsh int ipv4 show dynamicport tcp | Out-String)
$nombres = [regex]::Matches($texte, '\d+')
if ($nombres.Count -ge 1) { return [int]$nombres[0].Value }
return 49152
}
function Get-OmikaPlagesReservees {
<#
.SYNOPSIS
Plages TCP reservees par Windows (Hyper-V / WSL / Docker peuvent en
reserver arbitrairement). Necessite une session elevee.
#>
$plages = @()
$ancien = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$lignes = netsh int ipv4 show excludedportrange protocol=tcp
}
finally {
$ErrorActionPreference = $ancien
}
foreach ($ligne in $lignes) {
$m = [regex]::Match([string]$ligne, '^\s*(\d{1,5})\s+(\d{1,5})\s*$')
if ($m.Success) {
$plages += [pscustomobject]@{
Debut = [int]$m.Groups[1].Value
Fin = [int]$m.Groups[2].Value
}
}
}
return $plages
}
function Test-OmikaPortLibre {
<#
.SYNOPSIS
Determine si un port TCP est utilisable sur ce serveur.
.DESCRIPTION
Quatre causes de refus sont testees :
1. un processus ecoute deja sur ce port ;
2. le port tombe dans une plage RESERVEE par Windows ;
3. le port tombe dans la plage EPHEMERE (une connexion sortante peut le prendre) ;
4. la reservation reelle sur 127.0.0.1 echoue.
Le test 4 est la preuve : le port est reellement reserve puis relache.
.OUTPUTS
PSCustomObject : Port, Libre (booleen), Raisons (texte).
#>
param(
[Parameter(Mandatory = $true)]
[ValidateRange(1, 65535)]
[int]$Port,
[switch]$VerifierPlagesReservees
)
$refus = New-Object System.Collections.Generic.List[string]
# --- 1. Deja en ecoute ? -----------------------------------------------
$ecoute = @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue)
if ($ecoute.Count -gt 0) {
$qui = @()
foreach ($connexion in $ecoute) {
$processus = Get-Process -Id $connexion.OwningProcess -ErrorAction SilentlyContinue
if ($processus) { $qui += ('{0} (PID {1})' -f $processus.ProcessName, $connexion.OwningProcess) }
else { $qui += ('PID {0}' -f $connexion.OwningProcess) }
}
$refus.Add('deja en ecoute par ' + (($qui | Select-Object -Unique) -join ', '))
}
# --- 2. Plage reservee ? -----------------------------------------------
if ($VerifierPlagesReservees) {
foreach ($plage in (Get-OmikaPlagesReservees)) {
if ($Port -ge $plage.Debut -and $Port -le $plage.Fin) {
$refus.Add(('dans une plage RESERVEE par Windows ({0} a {1})' -f $plage.Debut, $plage.Fin))
}
}
}
# --- 3. Plage ephemere ? ----------------------------------------------
$debutEphemere = Get-OmikaDebutPlageEphemere
if ($Port -ge $debutEphemere) {
$refus.Add(('dans la plage EPHEMERE (a partir de {0}) : une connexion sortante peut le prendre' -f $debutEphemere))
}
# --- 4. Test reel : reserver le port sur 127.0.0.1 ---------------------
$verrou = $null
try {
$verrou = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, $Port)
$verrou.Start()
}
catch {
$refus.Add("reservation impossible : $($_.Exception.Message)")
}
finally {
if ($verrou) { try { $verrou.Stop() } catch { } }
}
return [pscustomobject]@{
Port = $Port
Libre = ($refus.Count -eq 0)
Raisons = ($refus -join ' ; ')
}
}
Export-ModuleMember -Function @(
'Write-OmikaStep',
'Write-OmikaInfo',
'Write-OmikaOk',
'Write-OmikaWarn',
'Write-OmikaFail',
'Assert-OmikaAdmin',
'Get-OmikaPaths',
'New-OmikaDirectory',
'Resolve-OmikaAccountName',
'Test-OmikaLocalAccount',
'Get-OmikaServiceLogonRight',
'Test-OmikaServiceLogonRight',
'Assert-OmikaServiceAccount',
'Grant-OmikaAcl',
'Get-OmikaServiceState',
'Install-OmikaService',
'Remove-OmikaService',
'Write-OmikaServiceSummary',
'Get-OmikaDebutPlageEphemere',
'Get-OmikaPlagesReservees',
'Test-OmikaPortLibre'
)