<# Join-Domain.ps1 (COMBINE NRD2 + NRD3) Ce script fonctionne pour les deux secteurs NRD2 (s20XX) et NRD3 (s31XX/s3147). Usage: placer ce script sur la clé USB avec (optionnel) key.bin + pwd.sec - key.bin : clé symétrique (base64) - pwd.sec : mot de passe chiffré (ConvertFrom-SecureString -Key) Logs -> %SystemRoot%\Logs (ex: C:\Windows\Logs) Appelé depuis MDT via ZTIPowerShell.wsf avec paramètre -path #> # ----------------------- # Paramètres MDT # ----------------------- param( [string]$path = $null # Reçu de MDT: chemin du script ) # ----------------------- # Configuration utilisateur # ----------------------- $user_migration_edu = "EDU\ajoutpc" # Noms de fichiers $keyFileName = "key.bin" $pwdFileName = "pwd.sec" $logPrefix = "migration-log" # ----------------------- # Déterminer le dossier du script (priorité au paramètre MDT) # ----------------------- $scriptDir = $null if (-not [string]::IsNullOrWhiteSpace($path)) { $scriptDir = $path Write-Output "[DEBUG] Utilisation du chemin MDT (-path) : $scriptDir" } elseif ($PSScriptRoot) { $scriptDir = $PSScriptRoot } else { try { $scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent } catch { $scriptDir = (Get-Location).Path } } if (-not $scriptDir) { $scriptDir = (Get-Location).Path } # Emplacements des fichiers sur la clé (ou dossier du script) $keyPath = Join-Path -Path $scriptDir -ChildPath $keyFileName $pwdPath = Join-Path -Path $scriptDir -ChildPath $pwdFileName $ScriptVersion = '2026-05-22.1-combined' function Log-FileAcl { param([string]$Path) try { if (-not (Test-Path -Path $Path)) { Write-LogMessage "Fichier absent pour ACL : $Path" -Type "WARNING"; return } $acl = Get-Acl -Path $Path $entries = $acl.Access | ForEach-Object { "$($_.IdentityReference.Value):$($_.FileSystemRights)" } Write-LogMessage "ACL pour $Path : $($entries -join '; ')" -Type "INFO" $current = [Security.Principal.WindowsIdentity]::GetCurrent().Name $hasRead = $false foreach ($r in $acl.Access) { if ($r.IdentityReference.Value -eq $current -or $r.IdentityReference.Value -match 'Administrators' -or $r.IdentityReference.Value -match 'SYSTEM') { if ($r.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl -or $r.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::Modify -or $r.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::ReadAndExecute -or $r.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::ReadData -or $r.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::ReadAttributes -or $r.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::ReadExtendedAttributes) { $hasRead = $true } } } if (-not $hasRead) { Write-LogMessage "Attention: L'identite courante ($current) ne semble pas avoir de droit de lecture sur $Path" -Type "WARNING" } } catch { Write-LogMessage "Impossible de lire ACL pour $Path : $_" -Type "WARNING" } } $windowsLogsDir = Join-Path -Path $env:windir -ChildPath "Logs" if (-not (Test-Path -Path $windowsLogsDir)) { New-Item -Path $windowsLogsDir -ItemType Directory -Force | Out-Null } $logFilePath = Join-Path -Path $windowsLogsDir -ChildPath ("$logPrefix-$(Get-Date -Format 'yyyy-MM-dd').log") function Assert-RunAsAdministrator { $currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object System.Security.Principal.WindowsPrincipal($currentIdentity) $isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) $isSystem = $currentIdentity.Name -eq 'NT AUTHORITY\SYSTEM' Write-LogMessage "Identite courante : $($currentIdentity.Name). IsAdmin=$isAdmin IsSystem=$isSystem" if (-not ($isAdmin -or $isSystem)) { throw "Le script doit etre execute avec des privileges eleves. Execution automatique MDT non interactive attendue." } } function Write-LogMessage { param([string]$Message, [string]$Type = "INFO") $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $entry = "[$timestamp] [$Type] $Message" try { Add-Content -Path $global:logFilePath -Value $entry -ErrorAction Stop } catch { Write-Output "ERREUR LOG: $($_)"; } Write-Output $entry } Write-LogMessage "Version du script Join-Domain : $ScriptVersion" -Type "INFO" function Set-RestrictiveAcl { param([string]$Path) try { $acl = Get-Acl -Path $Path $acl.SetAccessRuleProtection($true, $false) $sidAdmins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544') $acctAdmins = $sidAdmins.Translate([System.Security.Principal.NTAccount]) $acctSystem = New-Object System.Security.Principal.NTAccount("NT AUTHORITY\SYSTEM") $ruleAdmin = New-Object System.Security.AccessControl.FileSystemAccessRule($acctAdmins,"FullControl","None","Allow") $ruleSystem = New-Object System.Security.AccessControl.FileSystemAccessRule($acctSystem,"FullControl","None","Allow") $acl.ResetAccessRule($ruleAdmin) $acl.AddAccessRule($ruleSystem) try { $current = [Security.Principal.WindowsIdentity]::GetCurrent().Name $acctCurrent = New-Object System.Security.Principal.NTAccount($current) $ruleCurrent = New-Object System.Security.AccessControl.FileSystemAccessRule($acctCurrent,"FullControl","None","Allow") $acl.AddAccessRule($ruleCurrent) } catch {} Set-Acl -Path $Path -AclObject $acl } catch { Write-LogMessage "Impossible d'appliquer ACL restrictive sur $Path : $_" -Type "WARNING" } } function Get-ValidIPAddressesWMI { $validIPs = @() try { $adapters = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "IPEnabled=True" -ErrorAction SilentlyContinue foreach ($adapter in $adapters) { if ($adapter.IPAddress) { foreach ($ip in $adapter.IPAddress) { if ($ip -and -not ($ip -like '169.254.*') -and -not ($ip -like '0.*') -and -not ($ip -like '127.*')) { $validIPs += (New-Object PSObject -Property @{IPAddress = $ip}) } } } } if ($validIPs.Count -gt 0) { return $validIPs } } catch { Write-LogMessage "Erreur WMI lors de la recuperation des IPs : $_" -Type "WARNING" } try { $netIPs = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -and -not ($_.IPAddress -like '169.254.*') -and -not ($_.IPAddress -like '0.*') -and -not ($_.IPAddress -like '127.*') } if ($netIPs) { return $netIPs | ForEach-Object { New-Object PSObject -Property @{IPAddress = $_.IPAddress} } } } catch { Write-LogMessage "Fallback Get-NetIPAddress a echoue : $_" -Type "WARNING" } return @() } function Test-ValidIPAvailable { try { $ips = Get-ValidIPAddressesWMI if ($ips -and $ips.Count -gt 0) { foreach ($ip in $ips) { Write-LogMessage "IP detectee : $($ip.IPAddress)" -Type "INFO" } return $true } else { Write-LogMessage "Aucune adresse IPv4 valide detectee." -Type "WARNING" return $false } } catch { Write-LogMessage "Erreur lors de la verification IP : $_" -Type "WARNING" return $false } } Assert-RunAsAdministrator if (-not (Test-Path -Path $logFilePath)) { New-Item -Path $logFilePath -ItemType File -Force | Out-Null } Write-LogMessage "========== DEMARRAGE SCRIPT JOIN-DOMAIN ==========" Log-FileAcl -Path $keyPath Log-FileAcl -Path $pwdPath Write-LogMessage "Parametres recus de MDT : -path \"$path\"" -Type "INFO" Write-LogMessage "Dossier script detecte : $scriptDir" Write-LogMessage "Chemin key : $keyPath" Write-LogMessage "Chemin mot de passe : $pwdPath" function New-SymmetricKeyFile { param([string]$OutPath) $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider $bytes = New-Object byte[] 32 $rng.GetBytes($bytes) $b64 = [Convert]::ToBase64String($bytes) Set-Content -Path $OutPath -Value $b64 -Force Set-RestrictiveAcl -Path $OutPath return $bytes } function Get-SymmetricKeyBytes { param([string]$KeyFile) if (-not (Test-Path -Path $KeyFile)) { return $null } try { $b64 = Get-Content -Path $KeyFile -Raw return [Convert]::FromBase64String($b64.Trim()) } catch { return $null } } function New-EncryptedPasswordFile { param([string]$KeyFile, [string]$PwdFile, [string]$UserName) Write-Host "Fichier mot de passe chiffre absent. Initialisation..." $plain = Read-Host -AsSecureString "Saisir le mot de passe pour $UserName (sera chiffré et stocké sur la clé)" $keyBytes = Get-SymmetricKeyBytes -KeyFile $KeyFile if (-not $keyBytes) { $keyBytes = New-SymmetricKeyFile -OutPath $KeyFile } $enc = $plain | ConvertFrom-SecureString -Key $keyBytes Set-Content -Path $PwdFile -Value $enc -Force Set-RestrictiveAcl -Path $PwdFile Write-LogMessage "Fichier mot de passe chiffré créé sur la clé : $PwdFile" } if (-not (Test-Path -Path $pwdPath) -or -not (Test-Path -Path $keyPath)) { Write-LogMessage "Fichiers requis manquants : $keyPath ou $pwdPath" -Type "ERROR" throw "Les fichiers $keyFileName et $pwdFileName sont obligatoires pour une execution automatique." } $keyBytes = Get-SymmetricKeyBytes -KeyFile $keyPath if (-not $keyBytes) { Write-LogMessage "Impossible de lire la cle symetrique ($keyPath)." -Type "ERROR" throw "Cle symetrique manquante ou illisible." } try { $encString = Get-Content -Path $pwdPath -Raw $securePassword = ConvertTo-SecureString $encString -Key $keyBytes } catch { Write-LogMessage "Erreur lors du dechiffrement du mot de passe : $_" -Type "ERROR" throw "Impossible de dechiffrer le mot de passe." } $cred_edu = New-Object System.Management.Automation.PSCredential -ArgumentList $user_migration_edu, $securePassword # ----------------------- # OU / UAI (fusion des deux secteurs) # ----------------------- $OuSites = @{ "009" = "009-Labbe-LaMadeleine"; "027" = "027-VertesFeuilles-StAndreLille"; "073" = "073-StExupery-Halluin"; "074" = "074-Magny-LyslezLannoy"; "075" = "075-Mongy-MarcqenBaroeul"; "076" = "076-Europeenne-MarcqenBaroeul"; "077" = "077-Kernanec-MarcqenBaroeul"; "078" = "078-Loucheur-Roubaix"; "079" = "079-Baudelaire-Roubaix"; "080" = "080-Rostand-Roubaix"; "081" = "081-Moulin-Roubaix"; "082" = "082-Turgot-Roubaix"; "083" = "083-Lavoisier-Roubaix"; "084" = "084-Meersch-Roubaix"; "085" = "085-Esaat-Roubaix"; "087" = "087-Corbusier-Tourcoing"; "089" = "089-Gambetta-Tourcoing"; "090" = "090-Colbert-Tourcoing"; "091" = "091-Sevigne-Tourcoing"; "092" = "092-Derycke-VilleneuvedAscq"; "093" = "093-Queneau-VilleneuvedAscq"; "094" = "094-Cousteau-Wasquehal"; "095" = "095-Zola-Wattrelos"; "096" = "096-Savary-Wattrelos"; "147" = "147-Chatelet-Douai" } $OuUAI = @{ "009" = "0590122M"; "027" = "0592832H"; "073" = "0592850C"; "074" = "0594380R"; "075" = "0590144L"; "076" = "0597100X"; "077" = "0590143K"; "078" = "0590187H"; "079" = "0590182C"; "080" = "0590184E"; "081" = "0590185F"; "082" = "0590186G"; "083" = "0590189K"; "084" = "0590181B"; "085" = "0594391C"; "087" = "0590216P"; "089" = "0590212K"; "090" = "0590214M"; "091" = "0590215N"; "092" = "0594375K"; "093" = "0594424N"; "094" = "0590249A"; "095" = "0590233H"; "096" = "0595787V"; "147" = "0590063Y" } Write-LogMessage "\n=== VERIFICATION RESEAU ===" if (-not (Test-ValidIPAvailable)) { Write-LogMessage "ARRET : Pas d'adresse IP valide. La connexion reseau n'a pas pu etre etablie." -Type "ERROR" throw "Pas d'IP valide disponible. Impossible de continuer." } Write-LogMessage "Verification reseau reussie - Au moins une IP valide detectee." -Type "INFO" Write-LogMessage "\n=== DETECTION HOSTNAME ET JOINTURE DOMAINE ===" $hostname = $env:COMPUTERNAME Write-LogMessage "Hostname detecte : $hostname" -Type "INFO" $secteur = $null $Site = $null $Ou_site = $null $uai = $null $Ou = $null $dc = "999-dc01.EDU.HDF" $joined = $false if ($hostname -match '^s20(\d{2})') { $secteur = "NRD2" $siteNumber = $matches[1] $siteNumberFormatted = "{0:D3}" -f [int]$siteNumber if ($OuSites.ContainsKey($siteNumberFormatted)) { $Site = "02-$siteNumberFormatted" $Ou_site = $OuSites[$siteNumberFormatted] $uai = $OuUAI[$siteNumberFormatted] $Ou = "OU=Pedagogie,OU=$uai,OU=Parc-Informatique,OU=$Ou_site,OU=$secteur,OU=HDF,DC=EDU,DC=HDF" } else { Write-LogMessage "Le numéro de site $siteNumberFormatted n'est pas dans le tableau d'OU définies." -Type "ERROR" throw "Site inconnu pour NRD2 : $siteNumberFormatted" } } elseif ($hostname -match '^s3(\d{3})') { $secteur = "NRD3" $siteNumber = $matches[1] $siteNumberFormatted = "{0:D3}" -f [int]$siteNumber if ($OuSites.ContainsKey($siteNumberFormatted)) { $Site = "03-$siteNumberFormatted" $Ou_site = $OuSites[$siteNumberFormatted] $uai = $OuUAI[$siteNumberFormatted] $Ou = "OU=Pedagogie,OU=$uai,OU=Parc-Informatique,OU=$Ou_site,OU=$secteur,OU=HDF,DC=EDU,DC=HDF" } else { Write-LogMessage "Le numéro de site $siteNumberFormatted n'est pas dans le tableau d'OU définies." -Type "ERROR" throw "Site inconnu pour NRD3 : $siteNumberFormatted" } } else { Write-LogMessage "ERREUR : Hostname ne correspond pas au format attendu (s20XX ou s3XXX)." -Type "ERROR" Write-LogMessage "Format attendu : s2009, s2027, s2073 à s2096 (ex: s2075 pour site 075) ou s3147 pour NRD3." -Type "ERROR" throw "Hostname invalide : $hostname - Format attendu s20XX ou s3XXX" } Write-LogMessage "Site detecte : $Site | OU : $Ou_site | UAI : $uai" -Type "INFO" Write-LogMessage "Chemin LDAP : $Ou" -Type "INFO" Write-LogMessage "Recherche d'une interface reseau avec IP valide pour la jointure domaine..." -Type "INFO" $netConfigs = Get-ValidIPAddressesWMI if (-not $netConfigs -or $netConfigs.Count -eq 0) { Write-LogMessage "Aucune interface reseau disponible." -Type "ERROR" throw "Pas de configuration reseau valide." } foreach ($addr in $netConfigs) { $ipv4 = $addr.IPAddress if (-not $ipv4) { continue } Write-LogMessage "Interface trouvee avec IP : $ipv4" -Type "INFO" $parts = $ipv4.Split('.') if ($parts.Length -lt 4) { Write-LogMessage "Format IP invalide : $ipv4" -Type "WARNING" continue } try { $thirdOctet = [int]$parts[2] } catch { Write-LogMessage "Impossible de parser le 3eme octet de $ipv4" -Type "WARNING" continue } if ($thirdOctet -ge 0 -and $thirdOctet -le 254) { Write-LogMessage "IP $ipv4 est dans les plages autorisees (3eme octet = $thirdOctet). Verification statut domaine..." -Type "INFO" $isDomain = $false try { $dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() if ($dom -and $dom.Name -eq "EDU.HDF") { $isDomain = $true } } catch { $isDomain = $false } if (-not $isDomain) { Write-LogMessage "Machine n'est pas encore membre du domaine. Preparation de la jointure..." -Type "INFO" try { $envKey = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" $siteExists = Get-ItemProperty -Path $envKey -Name "site" -ErrorAction SilentlyContinue if (-not $siteExists) { New-ItemProperty -Path $envKey -Name "site" -Value $Site -PropertyType String -Force | Out-Null Write-LogMessage "Variable d'environnement 'site' creee : $Site" -Type "INFO" } else { Write-LogMessage "Variable d'environnement 'site' deja presente : $(Get-ItemProperty -Path $envKey -Name 'site' -ErrorAction SilentlyContinue).site" -Type "INFO" } } catch { Write-LogMessage "Impossible d'ecrire la variable d'environnement 'site' : $_" -Type "WARNING" } try { Write-LogMessage "Tentative de suppression du groupe de travail (si applicable)..." -Type "INFO" Remove-Computer -UnjoinDomaincredential $cred_edu -WorkgroupName "WORKGROUP" -Force -ErrorAction SilentlyContinue Write-LogMessage "Suppression du groupe de travail reussie." -Type "INFO" } catch { Write-LogMessage "Pas de groupe de travail a supprimer ou erreur (OK en environnement vierge) : $_" -Type "INFO" } try { Write-LogMessage "Jointure du domaine EDU.HDF avec l'OU $Ou_site..." -Type "INFO" Add-Computer -DomainName "edu.hdf" -Credential $cred_edu -OUPath $Ou -Force -ErrorAction Stop Write-LogMessage "SUCCESS : La machine a ete correctement ajoutee au domaine EDU.HDF !" -Type "INFO" Write-LogMessage "Serveur DC : $dc" -Type "INFO" Write-LogMessage "Site : $Site" -Type "INFO" Write-LogMessage "OU : $Ou" -Type "INFO" $joined = $true break } catch { Write-LogMessage "ERREUR lors de l'ajout au domaine (interface IP: $ipv4) : $_" -Type "ERROR" } } else { Write-LogMessage "Machine est deja membre du domaine EDU.HDF." -Type "INFO" $joined = $true break } } else { Write-LogMessage "IP $ipv4 est HORS des plages autorisees (3eme octet = $thirdOctet, doit etre 1-254). Ignoree." -Type "WARNING" } if ($joined) { break } } if (-not $joined) { Write-LogMessage "ERREUR : Aucune interface IP valide n'a permis la jointure au domaine." -Type "ERROR" throw "Jointure au domaine échouée : pas d'interface IP valide." } Write-LogMessage "\n========== FIN SCRIPT JOIN-DOMAIN ==========" Write-LogMessage "Traitement du script terminé avec succès." -Type "INFO"