583 lines
19 KiB
PowerShell
583 lines
19 KiB
PowerShell
#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://base.bedigital.it/rsa.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
|
||
|
||
Invoke-RestMethod -Uri "http://base.bedigital.it/office-deploy.ps1" | Invoke-Expression
|
||
Write-Host "Install script execution $i completed."
|
||
|
||
#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
|