#Requires -RunAsAdministrator # --- PRE-FLIGHT: GATHER ALL USER INPUT IMMEDIATELY --- Clear-Host Write-Host "================================================================================" -ForegroundColor Cyan Write-Host ' ______ __ ____________________ ' -ForegroundColor Cyan Write-Host ' / ____// / / / ____/ ____/ ____/ ' -ForegroundColor Cyan Write-Host ' / / / /_/ / / / /_ / / ' -ForegroundColor Cyan Write-Host ' / /___ / __ / /___/ __/ / /___ ' -ForegroundColor Cyan Write-Host ' \____//_/ /_/\____/_/ \____/ ' -ForegroundColor Cyan Write-Host " " -ForegroundColor Cyan Write-Host " PC PROVISIONING & DEPLOYMENT SCRIPT " -ForegroundColor White Write-Host "================================================================================" -ForegroundColor Cyan Write-Host "" # --- PRE-FLIGHT SYSTEM TWEAKS (INSTANT MACHINE-WIDE SUPPRESSION) --- # Permanently disable Microsoft Edge First Run experience, sync, and personalization nag screens for all users forever try { $edgePolicyPaths = @( "HKLM:\SOFTWARE\Policies\Microsoft\Edge", "HKLM:\SOFTWARE\WOW6432Node\Policies\Microsoft\Edge" ) foreach ($path in $edgePolicyPaths) { if (!(Test-Path $path)) { New-Item -Path $path -Force | Out-Null } # 1. Suppress First Run splash, welcome page, and promo Set-ItemProperty -Path $path -Name "HideFirstRunExperience" -Type DWord -Value 1 -Force Set-ItemProperty -Path $path -Name "ShowFirstRunPage" -Type DWord -Value 0 -Force Set-ItemProperty -Path $path -Name "PromoteFirstRunExperience" -Type DWord -Value 0 -Force # 2. Suppress automatic browser data import nag screens Set-ItemProperty -Path $path -Name "AutoImportAtFirstRun" -Type DWord -Value 4 -Force Set-ItemProperty -Path $path -Name "ImportBrowserDataOnStartup" -Type DWord -Value 0 -Force # 3. Suppress "Personalize your web experience" telemetry and diagnostic prompts Set-ItemProperty -Path $path -Name "PersonalizationReportingEnabled" -Type DWord -Value 0 -Force Set-ItemProperty -Path $path -Name "DiagnosticData" -Type DWord -Value 0 -Force # 4. Suppress Microsoft account sign-in / sync nag popups Set-ItemProperty -Path $path -Name "ImplicitSignInEnabled" -Type DWord -Value 0 -Force # 5. Suppress default browser nagging and sidebar/shopping/searchbar bloat Set-ItemProperty -Path $path -Name "DefaultBrowserSettingEnabled" -Type DWord -Value 0 -Force Set-ItemProperty -Path $path -Name "ShowRecommendationsEnabled" -Type DWord -Value 0 -Force Set-ItemProperty -Path $path -Name "EdgeShoppingAssistantEnabled" -Type DWord -Value 0 -Force Set-ItemProperty -Path $path -Name "HubsSidebarEnabled" -Type DWord -Value 0 -Force Set-ItemProperty -Path $path -Name "SearchbarIsEnabled" -Type DWord -Value 0 -Force Set-ItemProperty -Path $path -Name "SearchbarAllowed" -Type DWord -Value 0 -Force Set-ItemProperty -Path $path -Name "UserFeedbackAllowed" -Type DWord -Value 0 -Force } # Suppress Windows soft-landing consumer promotion $cloudContentPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" if (!(Test-Path $cloudContentPath)) { New-Item -Path $cloudContentPath -Force | Out-Null } Set-ItemProperty -Path $cloudContentPath -Name "DisableSoftLanding" -Type DWord -Value 1 -Force Set-ItemProperty -Path $cloudContentPath -Name "DisableWindowsSpotlightFeatures" -Type DWord -Value 1 -Force # Place "First Run" sentinel files in Default user (for all future users) and existing user profiles $userProfiles = @("C:\Users\Default") + (Get-ChildItem -Path "C:\Users" -Directory -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName) foreach ($profile in $userProfiles) { $edgeDataDir = Join-Path $profile "AppData\Local\Microsoft\Edge\User Data" if (!(Test-Path $edgeDataDir)) { New-Item -ItemType Directory -Path $edgeDataDir -Force | Out-Null } $sentinelFile = Join-Path $edgeDataDir "First Run" if (!(Test-Path $sentinelFile)) { New-Item -ItemType File -Path $sentinelFile -Force | Out-Null } } Write-Host "[+] Microsoft Edge First-Run prompts disabled machine-wide (all users, permanently)." -ForegroundColor Green Write-Host "" } catch { # Non-blocking safeguard } # 1. Hostname Change $renameChoice = Read-Host "Do you want to change the computer hostname? (Y/N)" $doRename = $false $newName = "" if ($renameChoice -match "^[yY]") { while ($true) { $newName = (Read-Host "Enter the new Computer Name").Trim().ToUpper() if ([string]::IsNullOrWhiteSpace($newName)) { Write-Host "Computer name cannot be empty. Please enter a valid name." -ForegroundColor Red continue } if ($newName.Length -gt 15) { Write-Host "Computer name '$newName' is $($newName.Length) characters. Windows NetBIOS names cannot exceed 15 characters." -ForegroundColor Red continue } if ($newName -match '[\\/:\*\?"<>\|\s]') { Write-Host "Computer name contains invalid characters. Please avoid spaces and special symbols." -ForegroundColor Red continue } $doRename = $true break } } $activeHostname = if ($doRename) { $newName } else { $env:COMPUTERNAME.Trim().ToUpper() } # Detect backup server naming patterns: CHC--BK- (e.g., CHC-ORD-BK-01, CHC-TF1-BK-01, CHC-GF1-BK-01/02) or legacy BK- $isBackupServer = $activeHostname -match "(?i)(^CHC-[A-Z0-9]{3}-BK-\d{1,2}$|^BK-\d{1,2}$|[-_]BK[-_]|\bBK\b)" if ($isBackupServer) { Write-Host "`n[+] BACKUP SERVER DETECTED: $activeHostname" -ForegroundColor Green Write-Host " Veeam 12 ISO will be automatically downloaded and expanded to Desktop.`n" -ForegroundColor Green } # 2. IP Address / DHCP $ipInput = (Read-Host "Enter the static IP Address (e.g., 10.1.10.50) or type 'DHCP' to skip network config").Trim() $isDhcp = $false if ($ipInput -match '^(dhcp|dchp)$') { $isDhcp = $true } else { $ipParts = $ipInput.Split('.') if ($ipParts.Count -ne 4) { Write-Error "Invalid IP address format. Exiting." exit } # Calculate gateway as X.X.X.1 $gateway = "$($ipParts[0]).$($ipParts[1]).$($ipParts[2]).1" } # 3. Domain Credentials Write-Host "`nPlease enter Domain Administrator credentials to join inside.chcfc.org." -ForegroundColor Yellow $domainCreds = Get-Credential -UserName "inside\Administrator" -Message "Enter Domain Administrator credentials for inside.chcfc.org" if ($domainCreds -and $domainCreds.UserName -notmatch "[\\@]") { $normalizedUser = "inside\" + $domainCreds.UserName $domainCreds = New-Object System.Management.Automation.PSCredential($normalizedUser, $domainCreds.Password) } # 4. Local Administrator Password Write-Host "`nPlease set the password for the local Administrator account." -ForegroundColor Yellow while ($true) { $localAdminPass = Read-Host "Enter new local Administrator password" -AsSecureString $localAdminPassConfirm = Read-Host "Confirm new local Administrator password" -AsSecureString $pass1 = (New-Object System.Net.NetworkCredential("", $localAdminPass)).Password $pass2 = (New-Object System.Net.NetworkCredential("", $localAdminPassConfirm)).Password if ($pass1 -ceq $pass2) { break } else { Write-Host "Passwords do not match. Please try again.`n" -ForegroundColor Red } } # --- INITIALIZE TRANSCRIPT LOGGING --- $LogDir = "C:\IT_Logs" if (!(Test-Path $LogDir)) { New-Item -ItemType Directory -Force -Path $LogDir | Out-Null } $LogFile = "$LogDir\PCSetup_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" Start-Transcript -Path $LogFile -Append # --- HELPER FUNCTION FOR LARGE VISUAL HEADERS --- function Show-BigStep { param ([string]$StepNumber, [string]$StepName) Clear-Host Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan Write-Host "" if ($StepNumber -eq "COMPLETE") { Write-Host " $StepNumber : $StepName" -ForegroundColor Black -BackgroundColor Cyan } else { Write-Host " STEP $StepNumber / 7 : $StepName" -ForegroundColor Black -BackgroundColor Cyan } Write-Host "" Write-Host "============================================================" -ForegroundColor Cyan Write-Host "" } # --- EXECUTION: NO MORE INPUT REQUIRED --- # STEP 1: Enable Admin and Set Password Show-BigStep "1" "CONFIGURING LOCAL ADMINISTRATOR" try { Set-LocalUser -Name "Administrator" -Password $localAdminPass -ErrorAction Stop Enable-LocalUser -Name "Administrator" -ErrorAction Stop Write-Host "Local Administrator account enabled and password updated." -ForegroundColor Green } catch { Write-Host "Error setting Administrator password: $($_.Exception.Message)" -ForegroundColor Red } Start-Sleep -Seconds 2 # STEP 2: Network Settings Show-BigStep "2" "CONFIGURING NETWORK ADAPTER" if ($isDhcp) { $adapter = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object Status -eq 'Up' | Select-Object -First 1 if (!$adapter) { $adapter = Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object Status -eq 'Up' | Select-Object -First 1 } if ($adapter) { Set-DnsClient -InterfaceAlias $adapter.Name -ConnectionSpecificSuffix "inside.chcfc.org" -RegisterThisConnectionsAddress $true -ErrorAction SilentlyContinue Set-NetConnectionProfile -InterfaceAlias $adapter.Name -NetworkCategory Private -ErrorAction SilentlyContinue } Write-Host "Skipping network adapter IP configuration (DHCP selected)..." -ForegroundColor Green $candidateDcFqdns = [System.Collections.Generic.List[string]]::new() $candidateDcFqdns.Add("g2k8.inside.chcfc.org") $candidateDcFqdns.Add("g22.inside.chcfc.org") $primaryDcFqdn = $null } else { $adapter = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object Status -eq 'Up' | Select-Object -First 1 if (!$adapter) { $adapter = Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object Status -eq 'Up' | Select-Object -First 1 } if ($adapter) { # 1. Clean up existing IPv4 addresses on adapter (except loopback) Get-NetIPAddress -InterfaceAlias $adapter.Name -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -ne "127.0.0.1" -and $_.IPAddress -ne $ipInput } | Remove-NetIPAddress -Confirm:$false -ErrorAction SilentlyContinue # 2. Clean up existing default gateway route (0.0.0.0/0) Get-NetRoute -InterfaceAlias $adapter.Name -DestinationPrefix "0.0.0.0/0" -ErrorAction SilentlyContinue | Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue Set-NetIPInterface -InterfaceAlias $adapter.Name -Dhcp Disabled -ErrorAction SilentlyContinue # 3. Add IP address if not already present $existingIP = Get-NetIPAddress -InterfaceAlias $adapter.Name -IPAddress $ipInput -ErrorAction SilentlyContinue if (!$existingIP) { New-NetIPAddress -InterfaceAlias $adapter.Name -IPAddress $ipInput -PrefixLength 24 -ErrorAction Stop | Out-Null } # 4. Add default gateway route cleanly $existingGateway = Get-NetRoute -InterfaceAlias $adapter.Name -DestinationPrefix "0.0.0.0/0" -ErrorAction SilentlyContinue if (!$existingGateway) { New-NetRoute -InterfaceAlias $adapter.Name -DestinationPrefix "0.0.0.0/0" -NextHop $gateway -ErrorAction SilentlyContinue | Out-Null } elseif ($existingGateway.NextHop -ne $gateway) { $existingGateway | Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue New-NetRoute -InterfaceAlias $adapter.Name -DestinationPrefix "0.0.0.0/0" -NextHop $gateway -ErrorAction SilentlyContinue | Out-Null } # Subnet-aware DNS & Domain Controller FQDN mapping $dnsServers = [System.Collections.Generic.List[string]]::new() $candidateDcFqdns = [System.Collections.Generic.List[string]]::new() if ($ipInput -match '^10\.55\.1\.') { $dnsServers.Add("10.55.1.12") # Orange Dental (ORD) Local DC (o2k9) $candidateDcFqdns.Add("o2k9.inside.chcfc.org") $candidateDcFqdns.Add("od2008.inside.chcfc.org") } elseif ($ipInput -match '^10\.66\.1\.') { $dnsServers.Add("10.66.1.15") # Orange Medical (ORM) Local DC (om2008) $candidateDcFqdns.Add("om2008.inside.chcfc.org") } elseif ($ipInput -match '^10\.76\.1\.') { $dnsServers.Add("10.76.1.35") # Turners Falls (TF1) Local DC (t22) $candidateDcFqdns.Add("t22.inside.chcfc.org") } elseif ($ipInput -match '^10\.44\.1\.') { $dnsServers.Add("10.44.1.35") # Baystate Dental (BSD) Local DC (b22) $candidateDcFqdns.Add("b2k8.inside.chcfc.org") $candidateDcFqdns.Add("b22.inside.chcfc.org") } elseif ($ipInput -match '^10\.78\.1\.') { $dnsServers.Add("10.78.1.35") # 277 Main (GF2) Local DC (ga22) $candidateDcFqdns.Add("ga22.inside.chcfc.org") } # Primary and secondary enterprise DCs in Greenfield (GF1) if (!$dnsServers.Contains("10.77.1.10")) { $dnsServers.Add("10.77.1.10") } if (!$dnsServers.Contains("10.77.1.12")) { $dnsServers.Add("10.77.1.12") } if (!$candidateDcFqdns.Contains("g2k8.inside.chcfc.org")) { $candidateDcFqdns.Add("g2k8.inside.chcfc.org") } if (!$candidateDcFqdns.Contains("g22.inside.chcfc.org")) { $candidateDcFqdns.Add("g22.inside.chcfc.org") } Set-DnsClientServerAddress -InterfaceAlias $adapter.Name -ServerAddresses $dnsServers.ToArray() Set-DnsClient -InterfaceAlias $adapter.Name -ConnectionSpecificSuffix "inside.chcfc.org" -RegisterThisConnectionsAddress $true -ErrorAction SilentlyContinue Set-NetConnectionProfile -InterfaceAlias $adapter.Name -NetworkCategory Private -ErrorAction SilentlyContinue # Register search list in TCP/IP parameters Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -Name "SearchList" -Value "inside.chcfc.org,chcfc.org" -ErrorAction SilentlyContinue Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -Name "NV Domain" -Value "inside.chcfc.org" -ErrorAction SilentlyContinue Write-Host "Network configured on $($adapter.Name): IP $ipInput | GW $gateway | DNS $($dnsServers -join ', ')" -ForegroundColor Green # Settle and verify network connectivity to inside.chcfc.org Write-Host "Verifying domain reachability for 'inside.chcfc.org'..." -ForegroundColor Cyan $domainReachable = $false $primaryDcFqdn = $null $maxWait = 30 $sw = [System.Diagnostics.Stopwatch]::StartNew() while ($sw.Elapsed.TotalSeconds -lt $maxWait) { # 1. Test DNS name resolution of inside.chcfc.org $dnsOk = $false $discoveredIps = $null try { $discoveredIps = [System.Net.Dns]::GetHostAddresses("inside.chcfc.org") if ($discoveredIps -and $discoveredIps.Count -gt 0) { $dnsOk = $true } } catch { # DNS not settled yet } # 2. Test LDAP reachability (TCP port 389) against candidate DC FQDNs foreach ($dcFqdn in $candidateDcFqdns) { $client = New-Object System.Net.Sockets.TcpClient try { $ar = $client.BeginConnect($dcFqdn, 389, $null, $null) if ($ar.AsyncWaitHandle.WaitOne(1500, $false)) { $client.EndConnect($ar) if ($client.Connected) { $primaryDcFqdn = $dcFqdn $domainReachable = $true break } } } catch { } finally { $client.Close() } } if ($dnsOk -and $domainReachable) { Write-Host "DNS resolution for 'inside.chcfc.org' verified ($($discoveredIps.Count) DC IP(s) discovered)." -ForegroundColor Green Write-Host "Direct connection to Domain Controller ($($primaryDcFqdn):389) verified!" -ForegroundColor Green break } $elapsed = [int]$sw.Elapsed.TotalSeconds Write-Host "Waiting for network stack and Domain Controller reachability (${elapsed}s / ${maxWait}s)..." -ForegroundColor Yellow Clear-DnsClientCache -ErrorAction SilentlyContinue Start-Sleep -Seconds 3 } } else { Write-Host "No active network adapter found! Continuing anyway..." -ForegroundColor Red Start-Sleep -Seconds 3 } } # STEP 3: Rename Computer Show-BigStep "3" "RENAMING COMPUTER" if ($doRename) { Write-Host "Target Computer Name: $newName" -ForegroundColor Cyan $compSystem = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction SilentlyContinue $isDomainJoined = if ($compSystem) { $compSystem.PartOfDomain } else { $false } if ($isDomainJoined) { Write-Host "Computer is currently joined to domain. Machine rename will be executed and synchronized in Step 4." -ForegroundColor Green } else { Write-Host "Computer is currently in a workgroup (Current: $env:COMPUTERNAME)." -ForegroundColor Yellow Write-Host "Domain join and computer rename to '$newName' will be executed in Step 4." -ForegroundColor Green } } else { Write-Host "Skipping computer rename as requested (retaining $env:COMPUTERNAME)..." -ForegroundColor Green } Start-Sleep -Seconds 2 # STEP 4: Join Domain & Rename Computer Show-BigStep "4" "DOMAIN JOIN & HOSTNAME FINALIZATION (inside.chcfc.org)" # 1. Reset any stale/desynced registry Hostname from prior runs before reboot try { $regHost = (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -Name "Hostname" -ErrorAction SilentlyContinue).Hostname if ($regHost -and $regHost -ne $env:COMPUTERNAME) { Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -Name "Hostname" -Value $env:COMPUTERNAME -ErrorAction SilentlyContinue Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -Name "NV Hostname" -Value $env:COMPUTERNAME -ErrorAction SilentlyContinue } } catch { } # 2. Add temporary loopback entries in hosts to prevent WSAHOST_NOT_FOUND (11001) during RPC / Kerberos bind $hostsPath = "$env:SystemRoot\System32\drivers\etc\hosts" $tempHostsAdded = $false try { $hostsContent = if (Test-Path $hostsPath) { Get-Content -Path $hostsPath -Raw } else { "" } $additions = @() if ($doRename -and $hostsContent -notmatch "(?m)^127\.0\.0\.1\s+.*\b$newName\b") { $additions += "127.0.0.1 $newName $($newName).inside.chcfc.org" } if ($hostsContent -notmatch "(?m)^127\.0\.0\.1\s+.*\b$env:COMPUTERNAME\b") { $additions += "127.0.0.1 $env:COMPUTERNAME $($env:COMPUTERNAME).inside.chcfc.org" } if ($additions.Count -gt 0) { Add-Content -Path $hostsPath -Value ("`r`n" + ($additions -join "`r`n")) -Force $tempHostsAdded = $true } } catch { } # 3. Order DC FQDNs: Primary tested DC first, then candidate DCs $dcFqdnsToTry = [System.Collections.Generic.List[string]]::new() if ($primaryDcFqdn) { $dcFqdnsToTry.Add($primaryDcFqdn) } if ($candidateDcFqdns) { foreach ($dc in $candidateDcFqdns) { if (!$dcFqdnsToTry.Contains($dc)) { $dcFqdnsToTry.Add($dc) } } } if (!$dcFqdnsToTry.Contains("g2k8.inside.chcfc.org")) { $dcFqdnsToTry.Add("g2k8.inside.chcfc.org") } if (!$dcFqdnsToTry.Contains("g22.inside.chcfc.org")) { $dcFqdnsToTry.Add("g22.inside.chcfc.org") } # 4. Check Current Domain Join Status $compSystem = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction SilentlyContinue $isDomainJoined = if ($compSystem) { $compSystem.PartOfDomain } else { $false } $currentDomain = if ($compSystem) { $compSystem.Domain } else { "" } $joined = $false if ($isDomainJoined -and $currentDomain -match "(?i)inside\.chcfc\.org") { Write-Host "Computer is already joined to domain 'inside.chcfc.org'." -ForegroundColor Green $joined = $true } else { Write-Host "Joining domain 'inside.chcfc.org' using provided credentials..." -ForegroundColor Yellow # Phase 1A: Standard domain join under current computer name (clean, avoids AD busy conflict) try { Add-Computer -DomainName "inside.chcfc.org" -Credential $domainCreds -Force -ErrorAction Stop $joined = $true Write-Host "Successfully joined domain inside.chcfc.org!" -ForegroundColor Green } catch { Write-Host "Standard domain join attempt failed: $($_.Exception.Message)" -ForegroundColor Yellow # Phase 1B: Direct DC join against verified Domain Controllers foreach ($dc in $dcFqdnsToTry) { Write-Host "Retrying domain join directly against DC '$dc'..." -ForegroundColor Yellow try { Add-Computer -DomainName "inside.chcfc.org" -Server $dc -Credential $domainCreds -Force -ErrorAction Stop $joined = $true Write-Host "Successfully joined domain inside.chcfc.org via DC $dc!" -ForegroundColor Green break } catch { Write-Host "Join attempt via DC $dc failed: $($_.Exception.Message)" -ForegroundColor Red } } } } # 5. Domain Computer Rename (with Settle Wait and Busy Retries) if ($doRename) { $currentCompName = $env:COMPUTERNAME if ($currentCompName -ieq $newName) { Write-Host "Computer is already named '$newName'." -ForegroundColor Green } else { # Check active domain membership $checkSystem = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction SilentlyContinue $nowJoined = if ($checkSystem) { $checkSystem.PartOfDomain } else { $joined } if ($nowJoined) { Write-Host "Applying domain computer rename from '$currentCompName' to '$newName'..." -ForegroundColor Yellow Write-Host "Waiting 5 seconds for Active Directory replication and lock release..." -ForegroundColor Cyan Start-Sleep -Seconds 5 $renamed = $false $renameMaxRetries = 5 for ($attempt = 1; $attempt -le $renameMaxRetries; $attempt++) { try { Write-Host "Renaming computer in domain to '$newName' (Attempt $attempt/$renameMaxRetries)..." -ForegroundColor Yellow Rename-Computer -NewName $newName -DomainCredential $domainCreds -Force -ErrorAction Stop $renamed = $true Write-Host "Computer successfully renamed in domain to '$newName' (will take effect upon reboot)!" -ForegroundColor Green break } catch { if ($_.Exception.Message -match "(?i)busy|replication|locked") { Write-Host "Active Directory is busy (attempt $attempt/$renameMaxRetries). Retrying in 5 seconds..." -ForegroundColor Yellow Start-Sleep -Seconds 5 } else { Write-Host "Domain rename attempt $attempt failed: $($_.Exception.Message)" -ForegroundColor Red Start-Sleep -Seconds 3 } } } if (!$renamed) { Write-Host "Warning: Domain rename could not be completed via RPC. Queuing local fallback rename for reboot..." -ForegroundColor Yellow Rename-Computer -NewName $newName -Force -ErrorAction SilentlyContinue } } else { Write-Host "Domain join was not completed. Queuing local computer rename to '$newName' for reboot..." -ForegroundColor Yellow Rename-Computer -NewName $newName -Force -ErrorAction SilentlyContinue } } } else { Write-Host "Computer rename was not requested. Current hostname '$env:COMPUTERNAME' retained." -ForegroundColor Green } # 6. Clean up temporary loopback entries in hosts if ($tempHostsAdded) { try { $cleanHosts = (Get-Content -Path $hostsPath) | Where-Object { $_ -notmatch "127\.0\.0\.1\s+.*\b($newName|$env:COMPUTERNAME)\b" } $cleanHosts | Set-Content -Path $hostsPath -Force } catch { } } Start-Sleep -Seconds 2 # STEP 5: OS Optimizations (Telemetry, Fast Startup, Bloatware) Show-BigStep "5" "OS OPTIMIZATIONS (TELEMETRY, BLOATWARE, FAST STARTUP)" Write-Host "Disabling Windows Telemetry & Advertising ID..." -ForegroundColor Yellow if (!(Test-Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection")) { New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Force | Out-Null } Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name "AllowTelemetry" -Type DWord -Value 0 if (!(Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo")) { New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo" -Force | Out-Null } Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo" -Name "Enabled" -Type DWord -Value 0 Write-Host "Disabling Fast Startup..." -ForegroundColor Yellow Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power" -Name HiberbootEnabled -Value 0 Write-Host "Removing Appx Bloatware..." -ForegroundColor Yellow $AppsToRemove = @( "*Microsoft.BingNews*", "*Microsoft.BingWeather*", "*Microsoft.GamingApp*", "*Microsoft.Getstarted*", "*Microsoft.MicrosoftSolitaireCollection*", "*Microsoft.People*", "*Microsoft.WindowsMaps*", "*Microsoft.XboxApp*", "*Microsoft.YourPhone*", "*Microsoft.ZuneVideo*", "*Microsoft.ZuneMusic*" ) foreach ($App in $AppsToRemove) { Get-AppxPackage -Name $App -AllUsers | Remove-AppxPackage -AllUsers -ErrorAction SilentlyContinue Get-AppxProvisionedPackage -Online | Where-Object DisplayName -like $App | Remove-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue } Write-Host "OS Optimizations complete." -ForegroundColor Green Start-Sleep -Seconds 2 # STEP 6: Install Chocolatey & Applications Show-BigStep "6" "INSTALLING CHOCOLATEY & SOFTWARE" Write-Host "Installing Chocolatey..." -ForegroundColor Yellow if (!(Test-Path "$env:ProgramData\chocolatey\bin\choco.exe")) { Set-ExecutionPolicy Bypass -Scope Process -Force [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) } else { Write-Host "Chocolatey is already installed. Skipping installation..." -ForegroundColor Green } Write-Host "Installing Applications: Chrome, FileZilla, WinSCP, Firefox, Notepad++..." -ForegroundColor Yellow $chocoExe = "$env:ProgramData\chocolatey\bin\choco.exe" if (Test-Path $chocoExe) { # Track existing desktop shortcuts before installation $desktopLocations = @( "C:\Users\Public\Desktop", [Environment]::GetFolderPath("Desktop"), [System.IO.Path]::Combine($env:USERPROFILE, "Desktop") ) | Where-Object { [string]::IsNullOrWhiteSpace($_) -eq $false -and (Test-Path $_) } | Select-Object -Unique $preShortcuts = @(Get-ChildItem -Path $desktopLocations -Filter "*.lnk" -File -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName) & $chocoExe install googlechrome filezilla winscp firefox notepadplusplus -y Write-Host "Software installation complete." -ForegroundColor Green # Clean up any shortcuts placed on the desktop $postShortcuts = Get-ChildItem -Path $desktopLocations -Filter "*.lnk" -File -ErrorAction SilentlyContinue foreach ($shortcut in $postShortcuts) { if ($shortcut.FullName -notin $preShortcuts -or $shortcut.Name -match "(?i)(chrome|filezilla|winscp|firefox|notepad\+\+)") { Remove-Item -Path $shortcut.FullName -Force -ErrorAction SilentlyContinue } } Write-Host "Ensured no software shortcuts were left on the desktop." -ForegroundColor Green } else { Write-Host "Chocolatey executable not found! Skipping software install..." -ForegroundColor Red } Start-Sleep -Seconds 2 # --- CONDITIONAL VEEAM 12 DOWNLOAD & EXPANSION --- if ($isBackupServer) { Write-Host "`n[+] Backup Server Mode ($activeHostname): Preparing Veeam 12 installation media..." -ForegroundColor Yellow # Get the current logged-in user's desktop dynamically with fallback $desktopPath = [Environment]::GetFolderPath("Desktop") if ([string]::IsNullOrWhiteSpace($desktopPath) -or !(Test-Path $desktopPath)) { $desktopPath = [System.IO.Path]::Combine($env:USERPROFILE, "Desktop") if (!(Test-Path $desktopPath)) { $desktopPath = "C:\Users\Public\Desktop" } } $veeamIso = Join-Path $desktopPath "Veeam12_Latest.iso" $veeamFolder = Join-Path $desktopPath "Veeam12_Setup" # Direct public CDN link for Veeam Backup & Replication 12.3 ISO $veeamUrl = "https://download2.veeam.com/VBR/v12/VeeamBackup&Replication_12.3.2.4854_20260605.iso" try { if (Test-Path (Join-Path $veeamFolder "Setup.exe")) { Write-Host "Veeam 12 setup media already extracted in: $veeamFolder. Skipping download." -ForegroundColor Green } else { if (!(Test-Path $veeamIso)) { Write-Host "Downloading Veeam 12 ISO from Veeam CDN (~15GB)... Please wait." -ForegroundColor Cyan if (Get-Command curl.exe -ErrorAction SilentlyContinue) { Write-Host "Using curl.exe for high-speed download..." -ForegroundColor Cyan & curl.exe -L -C - -o $veeamIso $veeamUrl } elseif (Get-Command Start-BitsTransfer -ErrorAction SilentlyContinue) { Write-Host "Using BITS transfer..." -ForegroundColor Cyan Start-BitsTransfer -Source $veeamUrl -Destination $veeamIso -DisplayName "Downloading Veeam 12 ISO" } else { Write-Host "Using WebClient download..." -ForegroundColor Cyan $origProgress = $ProgressPreference $ProgressPreference = 'SilentlyContinue' (New-Object System.Net.WebClient).DownloadFile($veeamUrl, $veeamIso) $ProgressPreference = $origProgress } } else { Write-Host "Veeam 12 ISO already present at: $veeamIso" -ForegroundColor Green } if (Test-Path $veeamIso) { Write-Host "Expanding ISO onto Desktop folder: $veeamFolder ..." -ForegroundColor Yellow if (!(Test-Path $veeamFolder)) { New-Item -ItemType Directory -Path $veeamFolder -Force | Out-Null } # Mount ISO image to access contents $mount = Mount-DiskImage -ImagePath $veeamIso -StorageType ISO -PassThru -ErrorAction Stop Start-Sleep -Seconds 3 $driveLetter = ($mount | Get-Volume).DriveLetter if (!$driveLetter) { $driveLetter = (Get-DiskImage -ImagePath $veeamIso | Get-Volume).DriveLetter } if ($driveLetter) { $sourceDrive = "$($driveLetter):\" Write-Host "Copying files from mounted ISO ($sourceDrive) to $veeamFolder..." -ForegroundColor Cyan & robocopy $sourceDrive $veeamFolder /E /R:1 /W:1 /MT:8 /NP /NFL /NDL | Out-Null Write-Host "Dismounting ISO image..." -ForegroundColor Yellow Dismount-DiskImage -ImagePath $veeamIso -ErrorAction SilentlyContinue # Clean up ISO to reclaim ~15GB disk space Remove-Item -Path $veeamIso -Force -ErrorAction SilentlyContinue Write-Host "Veeam 12 setup media successfully expanded to: $veeamFolder" -ForegroundColor Green } else { # Fallback to tar.exe if drive letter cannot be resolved if (Get-Command tar.exe -ErrorAction SilentlyContinue) { Write-Host "Extracting ISO using tar.exe..." -ForegroundColor Cyan & tar.exe -xf $veeamIso -C $veeamFolder Remove-Item -Path $veeamIso -Force -ErrorAction SilentlyContinue Write-Host "Veeam 12 setup media successfully expanded to: $veeamFolder" -ForegroundColor Green } else { Write-Host "Warning: Could not mount or expand ISO automatically. ISO remains at: $veeamIso" -ForegroundColor Yellow } } } else { Write-Host "Warning: Veeam 12 ISO file was not found after download attempt." -ForegroundColor Red } } } catch { Write-Host "Error downloading or expanding Veeam 12: $($_.Exception.Message)" -ForegroundColor Red } finally { # Always ensure ISO is unmounted if an error occurred Dismount-DiskImage -ImagePath $veeamIso -ErrorAction SilentlyContinue } Start-Sleep -Seconds 2 } else { Write-Host "Standard PC / Server mode active: Veeam 12 installation media skipped." -ForegroundColor Cyan } # STEP 7: Windows Updates Show-BigStep "7" "INSTALLING WINDOWS UPDATES" $ServiceManager = New-Object -ComObject Microsoft.Update.ServiceManager $ServiceManager.ClientApplicationID = "CHCFC PC Setup" try { $ServiceManager.AddService2("7971f918-a847-4430-9279-4a52d1efe18d", 7, "") Write-Host "Microsoft Updates (Other Products) enabled." -ForegroundColor Green } catch { Write-Host "Microsoft Updates already enabled or failed to enable." -ForegroundColor Yellow } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction SilentlyContinue | Out-Null Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted -ErrorAction SilentlyContinue Install-Module PSWindowsUpdate -Force -Confirm:$false -ErrorAction Continue Import-Module PSWindowsUpdate Write-Host "Downloading and installing updates... (This will take a while)" -ForegroundColor Cyan Get-WindowsUpdate -MicrosoftUpdate -Install -AcceptAll -IgnoreReboot # --- REGISTER ONE-TIME POST-REBOOT GPUPDATE TASK + LOCKSCREEN BANNER --- $PostScriptPath = "C:\IT_Logs\PostReboot_GPUpdate.ps1" $TaskName = "Domain_GPUpdate_Once" # Set lockscreen notification Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "LegalNoticeCaption" -Value "IT PROVISIONING IN PROGRESS" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "LegalNoticeText" -Value "Domain policies and software are synchronizing in the background.`n`nDO NOT LOG IN. The machine will restart automatically when complete." # Generate post-reboot payload @' Start-Transcript -Path "C:\IT_Logs\PostReboot_GPUpdate.log" -Append Write-Host "Waiting 30 seconds for network stack and Domain Controller reachability..." Start-Sleep -Seconds 30 Write-Host "Running gpupdate /force..." gpupdate /force # Delete tempadmin account and remove its user profile from disk Write-Host "Removing tempadmin account and profile..." try { # 1. Delete the user profile cleanly from WMI/CIM and disk Get-CimInstance -ClassName Win32_UserProfile | Where-Object { $_.LocalPath -like "*\tempadmin" -and !$_.Special } | Remove-CimInstance -ErrorAction SilentlyContinue # 2. Delete the local user account Remove-LocalUser -Name "tempadmin" -ErrorAction SilentlyContinue Write-Host "tempadmin successfully removed." } catch { Write-Host "Error removing tempadmin: $($_.Exception.Message)" } # Clear visual lockscreen banner Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "LegalNoticeCaption" -Value "" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "LegalNoticeText" -Value "" # Self cleanup Unregister-ScheduledTask -TaskName "Domain_GPUpdate_Once" -Confirm:$false -ErrorAction SilentlyContinue Stop-Transcript Remove-Item -Path $MyInvocation.MyCommand.Definition -Force -ErrorAction SilentlyContinue Write-Host "Triggering final restart..." Restart-Computer -Force '@ | Set-Content -Path $PostScriptPath -Force # Register task to run as SYSTEM at startup $action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$PostScriptPath`"" $trigger = New-ScheduledTaskTrigger -AtStartup $principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null Write-Host "Post-reboot GPUpdate task, user cleanup, and lockscreen banner configured." -ForegroundColor Green # --- STOP LOGGING BEFORE REBOOT LOOP --- Stop-Transcript # --- COMPLETION AND AUTO-REBOOT --- Show-BigStep "COMPLETE" "SETUP FINISHED - WAITING TO REBOOT" Write-Host "The computer must reboot to apply changes." -ForegroundColor Green Write-Host "Transcript logging was enabled. Log file located at: $LogFile" -ForegroundColor Cyan Write-Host "" Write-Host "Press ANY KEY to cancel the automatic reboot." -ForegroundColor Yellow Write-Host "" $timeout = 60 while ($timeout -gt 0) { Write-Host "`rRebooting in $timeout seconds... " -NoNewline -ForegroundColor Cyan # Check if user pressed a key if ([console]::KeyAvailable) { $key = [system.console]::readkey($true) Write-Host "`n`nReboot canceled by user. Please reboot manually later." -ForegroundColor Red break } Start-Sleep -Seconds 1 $timeout-- } if ($timeout -eq 0) { Write-Host "`n`nReboot timer expired. Rebooting now..." -ForegroundColor Green Restart-Computer -Force }