Files
base/base.ps1
2025-06-12 13:04:09 +02:00

672 lines
24 KiB
PowerShell
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#region Script Parameters
param (
[switch]$Debug,
[switch]$Run
)
if ($Debug) {
$DebugPreference = "Continue"
}
# Ensure script runs with admin privileges
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "Restarting script with elevated privileges..."
Start-Process powershell -ArgumentList "-ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Definition)`"" -Verb RunAs
exit
}
#endregion Script Parameters
#region Ensure WinGet is Updated with MSIXBUNDLE
# Function to download and install WinGet from MSIXBUNDLE
function Install-WinGetFromMSIXBUNDLE {
param (
[string]$url,
[string]$output
)
try {
Write-Host "Downloading WinGet from MSIXBUNDLE..."
Invoke-WebRequest -Uri $url -OutFile $output -ErrorAction Stop
Write-Host "Installing WinGet from MSIXBUNDLE..."
Add-AppxPackage -Path $output -ErrorAction Stop
Write-Host "WinGet installed successfully from MSIXBUNDLE."
return $true # Indicate success
} catch {
Write-Warning "Failed to install WinGet from MSIXBUNDLE: $($_.Exception.Message)"
return $false # Indicate failure
} finally {
# Clean up the downloaded file
if (Test-Path $output) {
Remove-Item $output -Force -ErrorAction SilentlyContinue
}
}
}
# Function to attempt WinGet source update
function Update-WinGetSources {
param (
[string]$url
)
try {
wRite-Host "Attempting to update WinGet sources..."
winget source update -ErrorAction Stop
Write-Host "WinGet sources updated successfully."
return $true #Indicate success
} catch {
Write-Warning "WinGet source update failed: $($_.Exception.Message)."
return $false # Indicate failure
}
}
# Function to check if a URL redirects to a .msixbundle file
function Test-UrlRedirectsToMSIXBUNDLE {
param (
[string]$url
)
try {
$response = Invoke-WebRequest -Uri $url -Method Head -UseBasicParsing -MaximumRedirection 0 -ErrorAction Stop
if ($response.StatusCode -eq 302 -or $response.StatusCode -eq 301) {
$redirectLocation = $response.Headers["Location"]
if ($redirectLocation -like "*.msixbundle") {
Write-Host "URL redirects to .msixbundle: $redirectLocation"
return $true
} else {
Write-Warning "URL does not redirect to .msixbundle. Redirects to: $redirectLocation"
return $false
}
} else {
Write-Warning "URL does not redirect. Status code: $($response.StatusCode)"
return $false
}
} catch {
Write-Warning "Error checking URL redirection: $($_.Exception.Message)"
return $false
}
}
# Define the aka.ms/getwinget URL
$msixBundleUrl = "https://aka.ms/getwinget"
# Specify the output file path
$msixBundleOutput = "$env:USERPROFILE\Downloads\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
# Attempt to update WinGet sources
$sourceUpdateResult = Update-WinGetSources -url $msixBundleUrl
# If updating sources failed, install from MSIXBUNDLE
if (-not $sourceUpdateResult) {
# Check if aka.ms/getwinget redirects to a .msixbundle file
$urlCheckResult = Test-UrlRedirectsToMSIXBUNDLE -url $msixBundleUrl
if ($urlCheckResult) {
Write-Warning "Attempting to install WinGet from MSIXBUNDLE using aka.ms/getwinget..."
$msixBundleInstallResult = Install-WinGetFromMSIXBUNDLE -url $msixBundleUrl -output $msixBundleOutput
if (-not $msixBundleInstallResult) {
Write-Error "Failed to install WinGet from MSIXBUNDLE. Script will continue, but WinGet may not function correctly."
}
} else {
Write-Error "aka.ms/getwinget does not redirect to a .msixbundle file. Cannot install WinGet."
}
}
#endregion Ensure WinGet is Updated with MSIXBUNDLE
#region Functions
# Function to prompt for input with a timeout
function Read-InputWithTimeout {
param (
[string]$Prompt,
[int]$TimeoutSeconds = 360 # 6 Minutes (6 * 60 seconds)
)
Write-Host $Prompt
$startTime = Get-Date
$input = $null # Initialize $input to null
while (((Get-Date) - $startTime).TotalSeconds -lt $TimeoutSeconds) {
if ($Host.UI.RawUI.KeyAvailable) {
$input = Read-Host
break # Exit the loop if input is received
}
Start-Sleep -Milliseconds 100 # Don't hog CPU
}
if (-not $input) {
Write-Host "Timeout expired or no input provided."
}
return $input
}
# Function to set registry value
function Set-RegistryValue {
param (
[string]$Path,
[string]$Name,
[object]$Value,
[ValidateSet(
"String",
"ExpandString",
"Binary",
"DWord",
"QWord",
"MultiString",
"Unknown"
)]
[string]$Type = "String"
)
try {
# Validate path format
if (-not $Path -match '^HK(LM|CU|CR|U):\\') {
throw "Invalid registry path format. Use HKLM:\ or HKCU:\ syntax"
}
# Create key if missing
if (-not (Test-Path $Path)) {
New-Item -Path $Path -Force | Out-Null
Write-Host "Created registry key: $Path"
}
# Set value with proper type handling
switch ($Type) {
"DWord" {
$Value = [int]$Value
}
"QWord" {
$Value = [long]$Value
}
"Binary" {
$Value = [byte[]]$Value
}
}
New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType $Type -Force -ErrorAction Stop
Write-Host "Set registry value: $Name = $Value ($Type) at $Path"
} catch {
Write-Warning "Registry operation failed: $_"
}
}
# Function to set Google NTP server
function Set-GoogleNTP {
Write-Host "Configuring time.google.com as NTP server..."
try {
# Configure NTP server
w32tm /config /manualpeerlist:"time.google.com" /syncfromflags:MANUAL
# Restart time service
Restart-Service -Name w32time -Force
# Force synchronization
w32tm /resync /force
Write-Host "Time synchronization completed successfully."
} catch {
Write-Host "Error configuring NTP: $($_.Exception.Message)"
}
}
# Function to get WinUtil Winget Latest
function Get-WinUtilWingetLatest {
try {
$wingetCmd = Get-Command winget -ErrorAction Stop
Write-Host "Updating WinGet using WinGet itself..."
$process = Start-Process -FilePath "winget" -ArgumentList "upgrade --id Microsoft.DesktopAppInstaller --silent --accept-package-agreements --accept-source-agreements" -Wait -NoNewWindow -PassThru
if ($process.ExitCode -ne 0) {
throw "WinGet upgrade failed with exit code $($process.ExitCode)"
}
return $true
} catch {
Write-Host "WinGet update failed: $($_.Exception.Message). Attempting to install manually..."
try {
$wingetUrl = "https://aka.ms/getwinget"
$tempFile = "$env:TEMP\Microsoft.DesktopAppInstaller.appxbundle"
Invoke-WebRequest -Uri $wingetUrl -OutFile $tempFile -ErrorAction Stop
Add-AppxPackage -Path $tempFile -ErrorAction Stop
# Check if WinGet is now available
if (Get-Command winget -ErrorAction SilentlyContinue) {
Write-Host "WinGet installed manually successfully."
return $true
} else {
Write-Error "WinGet manual install appeared to complete, but WinGet is not found."
return $false
}
} catch {
Write-Error "WinGet manual installation failed: $($_.Exception.Message)"
return $false
} finally {
Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
}
}
}
# Function to find a window by title
function Find-WindowByTitle {
param (
[string]$Title
)
[System.Windows.Forms.Application]::OpenForms | Where-Object {$_.Text -like "*$Title*" }
}
# Function to click a button
function Click-Button {
param (
[System.Windows.Forms.Form]$Form,
[string]$ButtonText
)
$button = $Form.Controls | Where-Object {$_.GetType().Name -eq "Button" -and $_.Text -like "*$ButtonText*" }
if ($button) {
$button.PerformClick()
}
}
#endregion Functions
#region Main Script
# Set Enforce TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Ensure WinGet is Updated with MSIXBUNDLE
# Attempt to update WinGet sources
if($sourceUpdateResult = Update-WinGetSources -url $msixBundleUrl){
}
# If updating sources failed, install from MSIXBUNDLE
else{
# Check if aka.ms/getwinget redirects to a .msixbundle file
if($urlCheckResult = Test-UrlRedirectsToMSIXBUNDLE -url $msixBundleUrl)
{
Write-Warning "Attempting to install WinGet from MSIXBUNDLE using aka.ms/getwinget..."
if ($msixBundleInstallResult = Install-WinGetFromMSIXBUNDLE -url $msixBundleUrl -output $msixBundleOutput)
{
}
else
{
Write-Error "Failed to install WinGet from MSIXBUNDLE. Script will continue, but WinGet may not function correctly."
}
}
else
{
Write-Error "aka.ms/getwinget does not redirect to a .msixbundle file. Cannot install WinGet."
}
}
# Set Google NTP
Set-GoogleNTP
# Create DWORD value
Set-RegistryValue -Path "HKLM:\Software\Policies\Microsoft\Windows\AppInstaller" -Name "EnableBypassCertificatePinningForMicrosoftStore" -Value 1 -Type "DWord"
# Install or Update WinGet
#Write-Host "Installing or Updating WinGet..."
#Get-WinUtilWingetLatest
# Refresh environment variables
$ENV:PATH = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
# Define a list of applications to install
$apps = @(
@{
Name = "Google Chrome"
Id = "Google.Chrome"
}
@{
Name = "7-Zip"
Id = "7zip.7zip"
}
@{
Name = "Adobe Acrobat Reader"
Id = "Adobe.Acrobat.Reader.64-bit"
}
)
# Applications to remove
$delnow = @(
@{
Name = "Microsoft Copilot"
Id = "9NHT9RB2F4HD"
}
@{
Name = "AI Shell"
Id = "Microsoft.AIShell"
}
@{
Name = "Microsoft Teams"
Id = "Microsoft.Teams"
}
@{
Name = "Microsoft Teams classic"
Id = "Microsoft.Teams.Classic"
}
@{
Name = "Microsoft Teams"
Id = "Microsoft.Teams.Free"
}
@{
Name = "Microsoft Teams"
Id = "XP8BT8DW290MPQ"
}
@{
Name = "Microsoft 365 Copilot"
Id = "9WZDNCRD29V9"
}
@{
Name = "Outlook for Windows"
Id = "9NRX63209R7B"
}
)
# Check manufacturer and install appropriate update utility
$manufacturer = (Get-CIMInstance Win32_ComputerSystem).Manufacturer
if ($manufacturer -match "Dell") {
if (-not (winget list --id Dell.CommandUpdate -q)) {
Write-Host "Installing Dell Command Update..."
winget install --id Dell.CommandUpdate --silent --accept-package-agreements --accept-source-agreements
} else {
Write-Host "Dell Command Update is already installed. Skipping."
}
} elseif ($manufacturer -match "Lenovo") {
if (-not (winget list --id Lenovo.Vantage -q)) {
Write-Host "Installing Lenovo Vantage..."
winget install --id Lenovo.Vantage --silent --accept-package-agreements --accept-source-agreements
} else {
Write-Host "Lenovo Vantage is already installed. Skipping."
}
} elseif ($manufacturer -match "Samsung") {
Write-Host "Running Samsung bloatware removal script..."
# Run removal script twice as requested
foreach ($i in 1..2) {
Invoke-RestMethod -Uri "http://172.16.16.89/removesamsungapps.ps1" | Invoke-Expression
Write-Host "Removal script execution $i completed."
}
} else {
Write-Host "No specific manufacturer update tool available. Skipping."
}
# Install applications using WinGet
foreach ($app in $apps) {
Write-Host "Installing $($app.Name)..."
Start-Process powershell -ArgumentList "-Command winget install --id $($app.Id) --silent --accept-package-agreements --accept-source-agreements"
}
foreach ($app in $apps) {
Write-Host "Installing $($app.Name)..."
winget install --id $($app.Id) --silent --accept-package-agreements --accept-source-agreements
}
# Remove unwanted applications
foreach ($app in $delnow) {
Write-Host "Removing $($app.Name)..."
winget uninstall --id $($app.Id) --silent
}
#region Install Office via ODT
Write-Host "Installing Office via Office Deployment Tool..."
# Install Office Deployment Tool using WinGet
try {
Write-Host "Installing Office Deployment Tool..."
# Ensure WinGet is available and working before this step
$process = Start-Process -FilePath "winget" -ArgumentList "install --id Microsoft.OfficeDeploymentTool --silent --accept-package-agreements --accept-source-agreements" -Wait -NoNewWindow -PassThru
if ($process.ExitCode -eq 0) {
Write-Host "Office Deployment Tool installed successfully."
} else {
Write-Warning "Failed to install Office Deployment Tool via WinGet. Exit code: $($process.ExitCode)"
# Consider adding more robust error handling or alternative methods if WinGet fails
exit # Exit if ODT cannot be installed
}
} catch {
Write-Error "An error occurred while trying to install Office Deployment Tool via WinGet: $($_.Exception.Message)"
exit
}
# Define known ODT installation path
$odtInstallDir = "C:\Program Files\OfficeDeploymentTool"
$odtExePath = Join-Path $odtInstallDir "setup.exe"
# Verify ODT executable exists at the expected path
if (-not (Test-Path $odtExePath)) {
Write-Error "Office Deployment Tool executable not found at expected path: $odtExePath. Please verify the WinGet installation or the path."
# Attempt to find it dynamically as a fallback, though less reliable
Write-Host "Attempting to locate setup.exe dynamically..."
$dynamicOdtExe = Get-Command "setup.exe" -ErrorAction SilentlyContinue | Where-Object { $_.Source -like "*OfficeDeploymentTool*" } | Select-Object -First 1
if ($dynamicOdtExe) {
$odtExePath = $dynamicOdtExe.Source
$odtInstallDir = Split-Path $odtExePath
Write-Warning "Found ODT at a dynamic path: $odtExePath. Proceeding with this path."
} else {
Write-Error "Could not locate ODT setup.exe dynamically either. Exiting Office installation."
# Skip Office installation or exit script, depending on desired behavior
# For now, we'll just write the error and the script will continue to the 'finally' block for cleanup.
# To stop further script execution for Office install:
# return # if in a function, or exit if appropriate for the whole script
# For this specific block, we'll let it fall through to the catch/finally
throw "ODT setup.exe not found." # This will be caught by the outer try/catch
}
}
# Define paths for the configuration XML in a temporary directory
$tempDirForXml = Join-Path $env:TEMP "OfficeODTConfig" # Using a slightly different name to avoid conflict if old $tempDir exists
$configXmlPath = Join-Path $tempDirForXml "BeConfig.xml"
$officeConfigUrl = "https://bestorageshare.blob.core.windows.net/office/BeConfig.xml" # Ensure this is defined earlier or here
try {
# Create temporary directory for XML if it doesn't exist
if (-not (Test-Path $tempDirForXml)) {
New-Item -Path $tempDirForXml -ItemType Directory -Force | Out-Null
Write-Host "Created temporary directory for XML: $tempDirForXml"
}
# Download Office configuration XML
Write-Host "Downloading Office configuration XML from $($officeConfigUrl)..."
Invoke-WebRequest -Uri $officeConfigUrl -OutFile $configXmlPath -ErrorAction Stop
Write-Host "Successfully downloaded XML to: $configXmlPath"
# Run ODT to download and install Office
Write-Host "Running Office Deployment Tool from '$odtExePath' to configure/install Office using '$configXmlPath'..."
Write-Host "Working directory will be set to '$odtInstallDir'."
# Ensure the XML path is quoted in the arguments
$odtArguments = "/configure `"$configXmlPath`""
$process = Start-Process -FilePath $odtExePath -ArgumentList $odtArguments -Wait -NoNewWindow -PassThru -WorkingDirectory $odtInstallDir
if ($process.ExitCode -eq 0) {
Write-Host "Office Deployment Tool completed successfully."
} else {
Write-Warning "Office Deployment Tool exited with code $($process.ExitCode). Office installation may have failed."
Write-Warning "Check ODT logs (usually in %TEMP% or %windir%\Temp, e.g., MACHINENAME-YYYYMMDD-HHMM.log) for more details."
}
} catch {
Write-Error "Failed to configure or install Office via ODT: $($_.Exception.Message)"
# Additional details from the exception might be useful:
# if ($_.Exception.InnerException) { Write-Error "Inner Exception: $($_.Exception.InnerException.Message)" }
} finally {
# Clean up temporary XML file and directory
Write-Host "Cleaning up temporary Office ODT configuration files..."
if (Test-Path $tempDirForXml) {
Start-Sleep -Seconds 1 # Brief pause
Remove-Item $tempDirForXml -Recurse -Force -ErrorAction SilentlyContinue
Write-Host "Cleaned up temporary directory: $tempDirForXml"
}
}
#endregion Install Office via ODT
#region McAfee Removal
# More reliable detection of McAfee products via registry
function Is-McAfeeInstalled {
$mcafeeRegPaths = @(
"HKLM:\SOFTWARE\McAfee",
"HKLM:\SOFTWARE\WOW6432Node\McAfee"
)
foreach ($path in $mcafeeRegPaths) {
if (Test-Path $path) {
return $true
}
}
return $false
}
if (Is-McAfeeInstalled) {
Write-Host "McAfee is detected. Proceeding with removal..."
# Download McAfee Removal Tool
$mcafeeRemovalTool = "$env:TEMP\MCPR.exe"
try {
Write-Host "Downloading McAfee Removal Tool..."
Invoke-WebRequest -Uri "https://download.mcafee.com/products/licensed/cust_support_patches/MCPR.exe" -OutFile $mcafeeRemovalTool -ErrorAction Stop
Write-Host "Starting McAfee Removal Tool..."
# Start the removal tool
$process = Start-Process -FilePath $mcafeeRemovalTool -Wait -PassThru
if ($process.ExitCode -eq 0) {
Write-Host "McAfee Removal Tool completed successfully."
} else {
Write-Warning "McAfee Removal Tool exited with code $($process.ExitCode). Manual removal may be required."
}
} catch {
Write-Warning "Failed to download or run McAfee Removal Tool: $($_.Exception.Message)"
} finally {
# Clean up the downloaded removal tool
if (Test-Path $mcafeeRemovalTool) {
Remove-Item $mcafeeRemovalTool -Force -ErrorAction SilentlyContinue
}
}
} else {
Write-Host "McAfee is not detected. Skipping removal."
}
#endregion McAfee Removal
# Current computer name
$currentName = $env:COMPUTERNAME
# Prompt for new hostname
Write-Host "Current hostname is '$currentName'."
$hostname = Read-Host "Enter new hostname (leave blank to keep current name)"
# Check if the input is non-empty and different
if (-not [string]::IsNullOrWhiteSpace($hostname) -and $hostname -ne $currentName) {
try {
Rename-Computer -NewName $hostname -Force -ErrorAction Stop
Write-Host "✅ Hostname changed to '$hostname'. It will take effect after reboot."
$global:hostnameChanged = $true
} catch {
Write-Warning "❌ Failed to rename computer: $($_.Exception.Message)"
}
} else {
Write-Host " No hostname change requested or hostname already set to '$currentName'. Skipping rename."
}
# Prompt for the Active Directory domain
$domain = Read-InputWithTimeout -Prompt "Enter the Active Directory domain name (leave blank for workgroup):" -TimeoutSeconds 360
if ($domain -match '\w+\.\w+') { # Basic validation for a domain-like format
Write-Host "Joining domain: $domain"
# Prompt for the DNS server IP
$dnsServer = Read-InputWithTimeout -Prompt "Enter the DNS server IP address:" -TimeoutSeconds 60
if ($dnsServer -match '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$') {
Write-Host "Setting DNS server to: $dnsServer"
# Get the network adapter
$adapter = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.IPEnabled -eq $true}
if ($adapter) {
try {
# Set the DNS server
$result = $adapter.SetDNSServerSearchOrder(@($dnsServer))
if ($result.ReturnValue -eq 0) {
Write-Host "DNS server set successfully."
} else {
Write-Warning "Failed to set DNS server. ReturnValue: $($result.ReturnValue)"
}
} catch {
Write-Warning "Error setting DNS server: $($_.Exception.Message)"
}
} else {
Write-Warning "No enabled network adapter found."
}
} else {
Write-Warning "Invalid DNS server IP address."
}
# Join the domain
try {
Add-Computer -DomainName $domain -Credential (Get-Credential) -ErrorAction Stop #removed restart, will perform later
Write-Host "Successfully joined domain: $domain. System will restart at the end of the script."
} catch {
Write-Warning "Failed to join domain: $($_.Exception.Message)"
}
} else {
Write-Host "No domain provided or invalid format. Skipping domain join and DNS configuration."
}
# Set password for user "admin" if it exists
if ($env:USERNAME -eq "admin") {
Write-Host "Setting password for user 'admin'..."
$securePassword = Read-Host "Enter password for 'admin'" -AsSecureString
$credential = New-Object System.Management.Automation.PSCredential("admin", $securePassword)
$credential.GetNetworkCredential().Password | Set-Content "$env:TEMP\temp_password.txt"
net user admin /passwordreq:yes | Out-Null
net user admin "$($credential.GetNetworkCredential().Password)" | Out-Null
Remove-Item "$env:TEMP\temp_password.txt" -Force -ErrorAction SilentlyContinue
Write-Host "Password for 'admin' has been set successfully.`n"
} else {
Write-Host "Current user is not 'admin'. Skipping password change.`n"
}
# Set suspension and screen turn-off time to unlimited
Write-Host "Setting suspension and screen turn-off time to unlimited..."
powercfg /change monitor-timeout-ac 0
powercfg /change monitor-timeout-dc 0
powercfg /change standby-timeout-ac 0
powercfg /change standby-timeout-dc 0
Write-Host "Suspension and screen turn-off time set to unlimited.`n"
# Update the system
Write-Host "Updating the system..."
try {
Start-Process -FilePath "powershell" -ArgumentList "-Command Install-WindowsUpdate -AcceptAll -AutoReboot" -Verb RunAs -Wait
Start-Process -FilePath "winget" -ArgumentList "upgrade --all" -Verb RunAs -Wait
Write-Host "System update completed successfully.`n"
} catch {
Write-Host "System update failed. Please check manually.`n"
}
Write-Host "All installations and updates completed. Restarting the system..."
# Restart computer
function Confirm-And-Reboot {
if ($global:hostnameChanged -or $domainJoined) {
Write-Host "System changes require a reboot."
$response = Read-InputWithTimeout -Prompt "Do you want to reboot the computer now? (Y/N):" -TimeoutSeconds 600
if ($response -match '^(Y|y)$') {
Write-Host "Rebooting now..."
Restart-Computer
} else {
Write-Host "Reboot skipped. Please remember to reboot manually for changes to take effect."
}
} else {
Write-Host "No reboot required."
}
}
Confirm-And-Reboot
#endregion Main Script