# Remediate-ComputerName.ps1 # Domain-wide: finds highest EA(L|D) in the entire domain and assigns next (floor at $StartNumber). # Stores AssignedName in registry for stability. Renames computer. No forced reboot. $StartNumber = 2001 $LabMode = $true $LabSuffix = "-LAB" $regPath = "HKLM:\SOFTWARE\EA\ComputerNaming" $modeTag = if ($LabMode) { "LAB" } else { "PROD" } $logDir = Join-Path $env:ProgramData "EA\Logs" $logFile = Join-Path $logDir "EA-Naming.log" try { New-Item -Path $logDir -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null } catch {} function Log { param([string]$Msg) $line = "$(Get-Date -Format s) $Msg" Write-Output $line try { Add-Content -Path $logFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue } catch {} } function Get-IsLaptop { $isLaptop = $false $chassisTypes = (Get-CimInstance -ClassName Win32_SystemEnclosure -ErrorAction SilentlyContinue).ChassisTypes if ($chassisTypes | Where-Object { $_ -in 8,9,10,14,30,31,32 }) { $isLaptop = $true } if (-not $isLaptop) { $isLaptop = @(Get-CimInstance -ClassName Win32_Battery -ErrorAction SilentlyContinue).Count -gt 0 } return $isLaptop } function Test-CompliantName { param( [string]$Name, [bool]$IsLaptop, [int]$StartNumber, [bool]$LabMode, [string]$LabSuffix ) $patterns = @() if ($LabMode) { # LabMode: Accept BOTH prod names and lab-suffixed names $patterns += '^EA(\d+)(L|D)$' $patterns += ('^EA(\d+)(L|D)' + [regex]::Escape($LabSuffix) + '$') } else { $patterns += '^EA(\d+)(L|D)$' } foreach ($rx in $patterns) { $m = [regex]::Match($Name, $rx, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) if ($m.Success) { $num = [int]$m.Groups[1].Value $suffix = $m.Groups[2].Value.ToUpperInvariant() if ($num -lt $StartNumber) { return $false } if ($IsLaptop -and $suffix -ne 'L') { return $false } if (-not $IsLaptop -and $suffix -ne 'D') { return $false } return $true } } return $false } function Get-DomainBaseDN { $root = [ADSI]"LDAP://RootDSE" return [string]$root.defaultNamingContext } function Get-HighestEaNumberDomainWide { param( [bool]$LabMode, [string]$LabSuffix ) $regex = if ($LabMode) { '^EA(\d+)(L|D)' + [regex]::Escape($LabSuffix) + '$' } else { '^EA(\d+)(L|D)$' } $base = Get-DomainBaseDN $entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$base") $searcher = New-Object System.DirectoryServices.DirectorySearcher($entry) $searcher.PageSize = 1000 if ($LabMode) { $searcher.Filter = "(&(objectCategory=computer)(name=EA*$LabSuffix))" } else { $searcher.Filter = "(&(objectCategory=computer)(name=EA*))" } $null = $searcher.PropertiesToLoad.Add("name") $maxNum = -1 $maxName = $null foreach ($r in $searcher.FindAll()) { $name = [string]$r.Properties["name"][0] if ($name -match $regex) { $num = [int]$Matches[1] if ($num -gt $maxNum) { $maxNum = $num; $maxName = $name } } } return [pscustomobject]@{ MaxNum = $maxNum; MaxName = $maxName; BaseDN = $base } } function Test-AdComputerNameExists { param([string]$Name) $base = Get-DomainBaseDN $entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$base") $searcher = New-Object System.DirectoryServices.DirectorySearcher($entry) $escaped = $Name.Replace('\','\\').Replace('(','\28').Replace(')','\29').Replace('*','\2a') $searcher.Filter = "(&(objectCategory=computer)(name=$escaped))" $searcher.PageSize = 1 return [bool]$searcher.FindOne() } try { Log "=== EA naming remediation start (domain-wide) ===" Log "Mode: $modeTag" Log "LanguageMode: $($ExecutionContext.SessionState.LanguageMode)" Log "Computer: $env:COMPUTERNAME" Log "User: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" Log "LogFile: $logFile" # Pending rename guard $activeName = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName' -ErrorAction SilentlyContinue).ComputerName $targetName = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName' -ErrorAction SilentlyContinue).ComputerName $pendingRename = ($activeName -and $targetName -and ($activeName -ne $targetName)) if ($pendingRename) { Log "Rename pending (Active: $activeName, Target: $targetName). Exiting." exit 0 } $isLaptop = Get-IsLaptop $suffix = if ($isLaptop) { "L" } else { "D" } Log "Device type suffix: $suffix (Laptop detected: $isLaptop)" # KEY BEHAVIOR YOU ASKED FOR: # If LabMode=true, we consider BOTH EA####L/D and EA####L/D-LAB compliant. # So remediation will only run (rename) if the name matches neither format. if (Test-CompliantName -Name $env:COMPUTERNAME -IsLaptop $isLaptop -StartNumber $StartNumber -LabMode $LabMode -LabSuffix $LabSuffix) { try { New-Item -Path $regPath -Force | Out-Null New-ItemProperty -Path $regPath -Name "AssignedName" -Value $env:COMPUTERNAME -PropertyType String -Force | Out-Null New-ItemProperty -Path $regPath -Name "AssignedMode" -Value $modeTag -PropertyType String -Force | Out-Null } catch {} Log "Already compliant (per current mode rules): $env:COMPUTERNAME" exit 0 } # If we already assigned earlier (and mode matches), reuse it $assignedName = $null $assignedMode = $null try { $p = Get-ItemProperty -Path $regPath -ErrorAction Stop $assignedName = $p.AssignedName $assignedMode = $p.AssignedMode } catch {} if (-not $assignedName -or $assignedMode -ne $modeTag) { if ($assignedName -and $assignedMode -and $assignedMode -ne $modeTag) { Log "Stored assignment exists but mode differs (Stored: $assignedMode, Current: $modeTag). Ignoring stored AssignedName." } $assignedName = $null Log "Not compliant. Querying AD for highest name in the CURRENT namespace..." $highest = Get-HighestEaNumberDomainWide -LabMode $LabMode -LabSuffix $LabSuffix Log "Search base: $($highest.BaseDN)" # If none found in this namespace, start from StartNumber $maxNum = [int]$highest.MaxNum if ($maxNum -lt 0) { Log "No matching EA objects found for this mode. Starting at $StartNumber." $maxNum = $StartNumber - 1 } else { Log "Highest found: $($highest.MaxName) (Number: $maxNum)" } $next = [Math]::Max($StartNumber, ($maxNum + 1)) # Pick first free candidate (domain-wide exact name check) for ($i = 0; $i -lt 2000; $i++) { $candidate = if ($LabMode) { "EA$next$suffix$LabSuffix" } else { "EA$next$suffix" } if ($candidate.Length -gt 15) { Log "ERROR: Candidate name too long (NetBIOS limit 15): $candidate" exit 0 } $exists = $false try { $exists = Test-AdComputerNameExists -Name $candidate } catch { $exists = $false } if (-not $exists) { $assignedName = $candidate break } $next++ } if (-not $assignedName) { Log "ERROR: Could not find a free name after 2000 attempts." exit 0 } # Store assignment for stability try { New-Item -Path $regPath -Force | Out-Null New-ItemProperty -Path $regPath -Name "AssignedName" -Value $assignedName -PropertyType String -Force | Out-Null New-ItemProperty -Path $regPath -Name "AssignedMode" -Value $modeTag -PropertyType String -Force | Out-Null New-ItemProperty -Path $regPath -Name "AssignedAt" -Value (Get-Date).ToString("s") -PropertyType String -Force | Out-Null } catch {} Log "AssignedName chosen: $assignedName" } else { Log "Using stored AssignedName: $assignedName (Mode: $assignedMode)" } if ($env:COMPUTERNAME -ieq $assignedName) { Log "Already named correctly." exit 0 } Log "Renaming computer from '$env:COMPUTERNAME' to '$assignedName'..." Rename-Computer -NewName $assignedName -Force Log "Rename queued. Will apply on next reboot." exit 0 } catch { Log "ERROR: Unhandled exception: $($_.Exception.Message)" Log "ERROR: $($_ | Out-String)" exit 0 }