#Requires -RunAsAdministrator <# Provision-ClientPC.ps1 What this script does: - Disables UAC (reboot required to fully apply) - Sets power settings: Laptop on battery: display off 30 min, sleep 60 min Plugged in: display never, sleep never Desktop: display never, sleep never - Installs (idempotent where practical): Adobe Acrobat Reader Google Chrome Lenovo Vantage (Lenovo only) Microsoft 365 Apps for business (via Office Deployment Tool) - Renames a SPECIFIED LOCAL user to a SPECIFIED username - Writes logs to C:\ProgramData\ClientProvisioning\provision.log Changes vs original: - No automatic username generation from BIOS serial - No use of $env:USERNAME to guess the account to rename - Prompts operator for: 1) current local username to rename 2) desired new local username - Adds rerun-safe install checks - Adds Office preflight / MSI removal handling - Tracks final step outcomes and prints a real summary Notes: - Renaming a local user does NOT rename the existing C:\Users\ folder. - Renaming will fail for Microsoft accounts / Entra / domain users. - Lenovo Vantage install is skipped on non-Lenovo hardware. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidatePattern('^[A-Za-z0-9_-]+$')] [string]$ClientAbbreviation, [Parameter(Mandatory = $false)] [string]$CurrentLocalUserName, [Parameter(Mandatory = $false)] [string]$NewLocalUserName, # Easter egg parameters [Parameter(Mandatory = $false)] [switch]$RishuMode, [Parameter(Mandatory = $false)] [switch]$HackerMode ) $ErrorActionPreference = 'Stop' # ----------------------------- # Easter Egg Functions # ----------------------------- function Show-Banner { $banner = @" ██╗ █████╗ ██╗ ███╗ ███╗███████╗██╗ ██╗ █████╗ ██╗ █████╗ ██║██╔══██╗██║ ████╗ ████║██╔════╝██║ ██║██╔══██╗██║ ██╔══██╗ ██║███████║██║ ██╔████╔██║█████╗ ██║ █╗ ██║███████║██║ ███████║ ██ ██║██╔══██║██║ ██║╚██╔╝██║██╔══╝ ██║███╗██║██╔══██║██║ ██╔══██║ ╚█████╔╝██║ ██║██║ ██║ ╚═╝ ██║███████╗╚███╔███╔╝██║ ██║███████╗██║ ██║ ╚════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ PC Provisioning Script v1.0 Made with <3 for Rishu "@ Write-Host $banner -ForegroundColor Cyan } function Show-HackerMode { Write-Host "`n[HACKER MODE ACTIVATED]" -ForegroundColor Green $matrixChars = "01アイウエオカキクケコサシスセソ" $fakeMessages = @( "Bypassing firewall...", "Injecting drivers...", "Decrypting Windows...", "Overclocking RAM... just kidding", "Installing Bitcoin miner... nah", "Contacting satellites...", "Hacking the mainframe...", "Uploading to NASA..." ) foreach ($msg in $fakeMessages) { Write-Host "[$(Get-Random)] " -NoNewline -ForegroundColor DarkGray for ($i = 0; $i -lt 5; $i++) { Write-Host ($matrixChars[(Get-Random -Maximum $matrixChars.Length)]) -NoNewline -ForegroundColor Green Start-Sleep -Milliseconds 20 } Write-Host " $msg" -ForegroundColor Green Start-Sleep -Milliseconds 300 } Write-Host "`n[Just kidding. Running normal provisioning...]`n" -ForegroundColor Yellow } function Get-RandomLoadingMessage { $messages = @( "Summoning digital elves...", "Teaching hamsters to type faster...", "Negotiating with Windows Update...", "Sacrificing coffee to the IT gods...", "Installing vibes...", "Loading awesomeness...", "Consulting the manual...", "RTFM... just kidding, there is no manual", "Procrastinating productively...", "Fighting with winget...", "Asking Clippy for help...", "Updating Adobe Reader (again)...", "Patching holes in the matrix..." ) return $messages[(Get-Random -Maximum $messages.Count)] } function Get-ManufacturerRoast { param([string]$Manufacturer) $roasts = @{ "Lenovo" = @("Think different? Nah, ThinkPad.", "Business laptop vibes only.", "The nipple mouse lives on.") "Dell" = @("Dude, you're getting a Dell!", "Enterprise gray is the new black.", "PowerShell approved.") "HP" = @("Hewlett-Packard? More like Hewlett-Packet.", "Built for business, roasting for fun.") "ASUS" = @("Republic of Gamers? More like Republic of Office Workers now.") "Acer" = @("Affordable and proud.", "The underdog story continues.") "Microsoft" = @("Surface level excellence.", "Native Windows experience activated.") "Apple" = @("Wait, how are you running this?", "Boot Camp survivor detected!") "Google" = @("Chromebook running Windows? Impressive.", "The Pixel of PCs.") "default" = @("Mystery machine detected.", "Unknown hardware, maximum respect.") } $key = $roasts.Keys | Where-Object { $Manufacturer -match $_ } | Select-Object -First 1 if (-not $key) { $key = "default" } return $roasts[$key][(Get-Random -Maximum $roasts[$key].Count)] } function Show-RishuMode { Write-Host "" Write-Host " *" -NoNewline -ForegroundColor Yellow Start-Sleep -Milliseconds 100 Write-Host " *" -NoNewline -ForegroundColor Cyan Start-Sleep -Milliseconds 100 Write-Host " *" -NoNewline -ForegroundColor Magenta Start-Sleep -Milliseconds 100 Write-Host " *" -NoNewline -ForegroundColor Green Start-Sleep -Milliseconds 100 Write-Host " *" -ForegroundColor Red Write-Host "" Write-Host " RISHU MODE: MAXIMUM OVERDRIVE" -ForegroundColor Magenta Write-Host " Speed: Over 9000" -ForegroundColor Yellow Write-Host " Coffee Level: Critical" -ForegroundColor Cyan Write-Host " Fun Level: Maximum" -ForegroundColor Green Write-Host "" } # ----------------------------- # Show Easter Eggs # ----------------------------- Show-Banner if ($RishuMode) { Show-RishuMode } if ($HackerMode) { Show-HackerMode } # ----------------------------- # Paths / logging # ----------------------------- $BaseDir = 'C:\ProgramData\ClientProvisioning' $LogFile = Join-Path $BaseDir 'provision.log' $OdtDir = Join-Path $BaseDir 'ODT' $OdtExe = Join-Path $OdtDir 'setup.exe' $OdtCfg = Join-Path $OdtDir 'configuration.xml' New-Item -Path $BaseDir -ItemType Directory -Force | Out-Null New-Item -Path $OdtDir -ItemType Directory -Force | Out-Null # ----------------------------- # Result tracking # ----------------------------- $script:StepResults = [System.Collections.Generic.List[object]]::new() function Add-StepResult { param( [Parameter(Mandatory = $true)] [string]$Step, [Parameter(Mandatory = $true)] [ValidateSet('SUCCESS','WARN','FAILED','SKIPPED')] [string]$Status, [Parameter(Mandatory = $true)] [string]$Message ) $script:StepResults.Add([pscustomobject]@{ Step = $Step Status = $Status Message = $Message }) | Out-Null } function Write-Log { param( [string]$Message, [ValidateSet('INFO','WARN','ERROR')] [string]$Level = 'INFO' ) $line = "[{0}] [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message $line | Tee-Object -FilePath $LogFile -Append } function Invoke-Step { param( [Parameter(Mandatory = $true)] [string]$StepName, [Parameter(Mandatory = $true)] [scriptblock]$ScriptBlock, [switch]$Fatal ) try { & $ScriptBlock } catch { $msg = $_.Exception.Message Write-Log "$StepName failed: $msg" 'ERROR' Add-StepResult -Step $StepName -Status 'FAILED' -Message $msg if ($Fatal) { throw } } } function Test-IsAdmin { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Get-DeviceInfo { $cs = Get-CimInstance Win32_ComputerSystem $bios = Get-CimInstance Win32_BIOS $bb = Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue [pscustomobject]@{ Manufacturer = ($cs.Manufacturer | ForEach-Object { $_.Trim() }) Model = ($cs.Model | ForEach-Object { $_.Trim() }) SerialNumber = ($bios.SerialNumber | ForEach-Object { $_.Trim() }) IsLaptop = [bool]$bb } } function Test-ValidNewLocalUserName { param( [Parameter(Mandatory = $true)] [string]$UserName ) # Practical safe subset for local SAM names. # 20-char cap kept intentionally conservative for compatibility. if ([string]::IsNullOrWhiteSpace($UserName)) { return $false } if ($UserName.Length -gt 20) { return $false } if ($UserName -notmatch '^[A-Za-z0-9._-]+$') { return $false } return $true } function Read-RequiredInput { param( [Parameter(Mandatory = $true)] [string]$Prompt, [Parameter(Mandatory = $true)] [scriptblock]$ValidateScript, [Parameter(Mandatory = $true)] [string]$ValidationMessage ) while ($true) { $value = Read-Host $Prompt if (& $ValidateScript $value) { return $value } Write-Host $ValidationMessage -ForegroundColor Yellow Write-Log "Invalid operator input for prompt '$Prompt': '$value'" 'WARN' } } function Get-UninstallRegistryPaths { @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) } function Get-InstalledPrograms { $programs = foreach ($path in Get-UninstallRegistryPaths) { Get-ItemProperty -Path $path -ErrorAction SilentlyContinue | Where-Object { -not [string]::IsNullOrWhiteSpace($_.DisplayName) } | Select-Object DisplayName, DisplayVersion, Publisher, InstallLocation, PSChildName } $programs | Sort-Object DisplayName -Unique } function Test-ProgramInstalled { param( [Parameter(Mandatory = $true)] [string[]]$NamePatterns ) $programs = Get-InstalledPrograms foreach ($program in $programs) { foreach ($pattern in $NamePatterns) { if ($program.DisplayName -match $pattern) { return $true } } } return $false } function Get-WingetPath { $cmd = Get-Command winget.exe -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } $appInstaller = Get-AppxPackage -AllUsers Microsoft.DesktopAppInstaller -ErrorAction SilentlyContinue | Sort-Object Version -Descending | Select-Object -First 1 if ($appInstaller -and $appInstaller.InstallLocation) { $candidate = Join-Path $appInstaller.InstallLocation 'winget.exe' if (Test-Path $candidate) { return $candidate } } $fallbacks = @( 'C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*\winget.exe', "$env:ProgramFiles\WindowsApps\Microsoft.DesktopAppInstaller_*\winget.exe" ) foreach ($pattern in $fallbacks) { $resolved = Get-ChildItem -Path $pattern -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1 if ($resolved) { return $resolved.FullName } } throw "winget.exe not found. Install App Installer / Windows Package Manager first." } function Invoke-Winget { param( [Parameter(Mandatory = $true)] [string[]]$Arguments ) $winget = Get-WingetPath Write-Log "Running: winget $($Arguments -join ' ')" & $winget @Arguments $exitCode = $LASTEXITCODE if ($exitCode -ne 0) { throw "winget failed with exit code $exitCode. Args: $($Arguments -join ' ')" } } function Install-WingetPackageIfMissing { param( [Parameter(Mandatory = $true)] [string]$PackageId, [Parameter(Mandatory = $true)] [string]$FriendlyName, [Parameter(Mandatory = $true)] [string[]]$DetectionPatterns, [string]$Source ) if (Test-ProgramInstalled -NamePatterns $DetectionPatterns) { Write-Log "$FriendlyName already installed; skipping" Add-StepResult -Step $FriendlyName -Status 'SKIPPED' -Message 'Already installed' return } $args = @( 'install', '--id', $PackageId, '--exact', '--silent', '--accept-source-agreements', '--accept-package-agreements', '--disable-interactivity' ) if ($Source) { $args += @('--source', $Source) } Invoke-Winget -Arguments $args if (Test-ProgramInstalled -NamePatterns $DetectionPatterns) { Write-Log "$FriendlyName installed successfully" Add-StepResult -Step $FriendlyName -Status 'SUCCESS' -Message 'Installed' } else { throw "$FriendlyName install command completed, but the application was not detected afterward." } } function Set-UacDisabled { Write-Log "Disabling UAC" Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name 'EnableLUA' -Value 0 Add-StepResult -Step 'Disable UAC' -Status 'SUCCESS' -Message 'EnableLUA set to 0 (reboot required)' } function Set-PowerConfig { param( [bool]$IsLaptop ) Write-Log "Applying power configuration. IsLaptop=$IsLaptop" if ($IsLaptop) { # On battery powercfg /change monitor-timeout-dc 30 | Out-Null powercfg /change standby-timeout-dc 60 | Out-Null # Plugged in powercfg /change monitor-timeout-ac 0 | Out-Null powercfg /change standby-timeout-ac 0 | Out-Null } else { # Desktop: never powercfg /change monitor-timeout-ac 0 | Out-Null powercfg /change standby-timeout-ac 0 | Out-Null powercfg /change monitor-timeout-dc 0 | Out-Null powercfg /change standby-timeout-dc 0 | Out-Null } Add-StepResult -Step 'Power Configuration' -Status 'SUCCESS' -Message "Applied power settings (IsLaptop=$IsLaptop)" } function Get-OfficeInstallState { $programs = Get-InstalledPrograms $officePrograms = $programs | Where-Object { $_.DisplayName -match 'Microsoft 365|Office 365|Microsoft Office|Office Professional|Office Standard|Office Home|Office Business|Visio|Project' } $clickToRunConfig = 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration' $clickToRun = $null if (Test-Path $clickToRunConfig) { $clickToRun = Get-ItemProperty -Path $clickToRunConfig -ErrorAction SilentlyContinue } $isBusinessRetailInstalled = $false if ($clickToRun) { $productIds = @( $clickToRun.ProductReleaseIds, $clickToRun.OProductReleaseIds ) | Where-Object { $_ } if (($productIds -join ';') -match 'O365BusinessRetail') { $isBusinessRetailInstalled = $true } } $hasClickToRun = [bool]$clickToRun $hasMsiOffice = [bool]($officePrograms | Where-Object { $_.Publisher -match 'Microsoft' -and $_.DisplayName -match 'Office' -and $_.PSChildName -notmatch '^O365' }) [pscustomobject]@{ HasAnyOffice = [bool]$officePrograms HasClickToRun = $hasClickToRun HasMsiOffice = $hasMsiOffice IsBusinessRetailInstalled = $isBusinessRetailInstalled InstalledProducts = $officePrograms } } function Install-OfficeBusiness { Write-Log "Starting Office preflight" $state = Get-OfficeInstallState if ($state.IsBusinessRetailInstalled) { Write-Log "Microsoft 365 Apps for business already installed; skipping" Add-StepResult -Step 'Microsoft 365 Apps for business' -Status 'SKIPPED' -Message 'Already installed' return } if ($state.HasClickToRun -and -not $state.IsBusinessRetailInstalled) { $products = ($state.InstalledProducts | Select-Object -ExpandProperty DisplayName -Unique) -join '; ' throw "Existing Click-to-Run Office installation detected that is not O365BusinessRetail. Review/remediate manually before continuing. Detected: $products" } Write-Log "Installing Microsoft 365 Apps for business via Office Deployment Tool" $odtDownloadUrl = 'https://officecdn.microsoft.com/pr/wsus/setup.exe' Invoke-WebRequest -Uri $odtDownloadUrl -OutFile $OdtExe $xml = @" "@ Set-Content -Path $OdtCfg -Value $xml -Encoding UTF8 Push-Location $OdtDir try { & $OdtExe /configure $OdtCfg $exitCode = $LASTEXITCODE if ($exitCode -ne 0) { throw "ODT exited with code $exitCode" } } finally { Pop-Location } Start-Sleep -Seconds 5 $stateAfter = Get-OfficeInstallState if ($stateAfter.IsBusinessRetailInstalled) { Write-Log "Microsoft 365 Apps for business install completed successfully" Add-StepResult -Step 'Microsoft 365 Apps for business' -Status 'SUCCESS' -Message 'Installed' } else { throw "Office install command completed, but O365BusinessRetail was not detected afterward." } } function Rename-SpecifiedLocalUser { param( [Parameter(Mandatory = $true)] [string]$CurrentUserName, [Parameter(Mandatory = $true)] [string]$NewUserName ) Write-Log "Attempting local user rename from '$CurrentUserName' to '$NewUserName'" $localUser = Get-LocalUser -Name $CurrentUserName -ErrorAction Stop if ($localUser.PrincipalSource -ne 'Local') { throw "User '$CurrentUserName' is not a local account." } if ($localUser.Name -eq $NewUserName) { Write-Log "Local user already has desired name '$NewUserName'" Add-StepResult -Step 'Rename Local User' -Status 'SKIPPED' -Message "User already named '$NewUserName'" return } if (Get-LocalUser -Name $NewUserName -ErrorAction SilentlyContinue) { throw "A local user named '$NewUserName' already exists." } Rename-LocalUser -Name $CurrentUserName -NewName $NewUserName $renamedUser = Get-LocalUser -Name $NewUserName -ErrorAction SilentlyContinue if (-not $renamedUser) { throw "Rename command returned, but '$NewUserName' was not found afterward." } Write-Log "Renamed local user to '$NewUserName'" Add-StepResult -Step 'Rename Local User' -Status 'SUCCESS' -Message "'$CurrentUserName' renamed to '$NewUserName'" } function Write-FinalSummary { Write-Host '' Write-Host '========================================' -ForegroundColor Cyan Write-Host ' FINAL SUMMARY ' -ForegroundColor Cyan Write-Host '========================================' -ForegroundColor Cyan foreach ($result in $script:StepResults) { $color = switch ($result.Status) { 'SUCCESS' { 'Green' } 'SKIPPED' { 'Yellow' } 'WARN' { 'Yellow' } 'FAILED' { 'Red' } default { 'White' } } $icon = switch ($result.Status) { 'SUCCESS' { '[+]' } 'SKIPPED' { '[~]' } 'WARN' { '[!]' } 'FAILED' { '[x]' } default { '[?]' } } Write-Host " $icon $($result.Step): $($result.Message)" -ForegroundColor $color } $failedCount = @($script:StepResults | Where-Object Status -eq 'FAILED').Count $warnCount = @($script:StepResults | Where-Object Status -eq 'WARN').Count $successCount = @($script:StepResults | Where-Object Status -eq 'SUCCESS').Count Write-Host '' Write-Host '========================================' -ForegroundColor Cyan if ($failedCount -gt 0) { $failMessages = @( "Well, that didn't go as planned...", "Houston, we have a problem.", "Some things broke. But hey, we tried!", "Failed spectaculary. Time for coffee.", "Error 418: I'm a teapot. Also, some stuff failed." ) Write-Host " $($failMessages[(Get-Random -Maximum $failMessages.Count)])" -ForegroundColor Red Write-Host " Failures: $failedCount | Warnings: $warnCount | Successes: $successCount" -ForegroundColor Red } elseif ($warnCount -gt 0) { $warnMessages = @( "Close enough! Some warnings, but we made it.", "Success... ish. Check the warnings.", "Not bad! A few bumps along the way.", "B+ for effort. Warnings detected.", "It works! Mostly. Check the logs." ) Write-Host " $($warnMessages[(Get-Random -Maximum $warnMessages.Count)])" -ForegroundColor Yellow Write-Host " Warnings: $warnCount | Successes: $successCount" -ForegroundColor Yellow } else { $successMessages = @( "Nailed it! PC is ready to rock.", "Perfect landing! Everything installed smoothly.", "Success! Your PC is now 100% more provisioned.", "GG WP! All systems operational.", "Achievement unlocked: PC Provisioned!", "You're all set! Go conquer the world.", "*Chef's kiss* Perfection." ) Write-Host " $($successMessages[(Get-Random -Maximum $successMessages.Count)])" -ForegroundColor Green Write-Host " All $successCount steps completed successfully!" -ForegroundColor Green } Write-Host '' Write-Host " Log file: $LogFile" -ForegroundColor DarkGray Write-Host " Reboot required for UAC change." -ForegroundColor DarkGray # Fun footer $footers = @( "Thanks for using Rishu's Provisioning Script!", "Made with coffee and PowerShell.", "Remember: Ctrl+C is always an option.", "Pro tip: Always read the logs.", "Have a great day! Or don't. I'm just a script." ) Write-Host "`n $($footers[(Get-Random -Maximum $footers.Count)])`n" -ForegroundColor DarkGray Write-Host '========================================' -ForegroundColor Cyan } # ----------------------------- # Main # ----------------------------- if (-not (Test-IsAdmin)) { throw "Run this script from an elevated PowerShell session." } Start-Transcript -Path (Join-Path $BaseDir 'transcript.txt') -Force | Out-Null try { Write-Log "Starting provisioning" $device = Get-DeviceInfo Write-Log "Manufacturer: $($device.Manufacturer)" Write-Log "Model: $($device.Model)" Write-Log "Serial: $($device.SerialNumber)" Write-Log "Client abbreviation: $ClientAbbreviation" # Easter egg: Manufacturer roast $roast = Get-ManufacturerRoast -Manufacturer $device.Manufacturer Write-Host "`n > $($device.Manufacturer) detected. $roast`n" -ForegroundColor Magenta if (-not $CurrentLocalUserName) { $CurrentLocalUserName = Read-RequiredInput ` -Prompt 'Enter the EXISTING LOCAL username to rename' ` -ValidateScript { param($v) -not [string]::IsNullOrWhiteSpace($v) } ` -ValidationMessage 'Current local username cannot be blank.' } if (-not $NewLocalUserName) { $NewLocalUserName = Read-RequiredInput ` -Prompt 'Enter the NEW LOCAL username (letters/numbers/._- only, max 20 chars)' ` -ValidateScript { param($v) Test-ValidNewLocalUserName -UserName $v } ` -ValidationMessage 'Invalid username. Use only letters, numbers, period, underscore, or hyphen; maximum 20 characters.' } elseif (-not (Test-ValidNewLocalUserName -UserName $NewLocalUserName)) { throw "NewLocalUserName is invalid. Use only letters, numbers, period, underscore, or hyphen; maximum 20 characters." } Write-Log "Current local username specified: $CurrentLocalUserName" Write-Log "New local username specified: $NewLocalUserName" # Reduce winget first-use friction try { Write-Host " > $(Get-RandomLoadingMessage)" -ForegroundColor DarkGray Write-Log "Refreshing winget sources" Invoke-Winget -Arguments @('source', 'update', '--accept-source-agreements') Add-StepResult -Step 'winget source update' -Status 'SUCCESS' -Message 'Sources refreshed' } catch { Write-Log "winget source update warning: $($_.Exception.Message)" 'WARN' Add-StepResult -Step 'winget source update' -Status 'WARN' -Message $_.Exception.Message } Invoke-Step -StepName 'Disable UAC' -Fatal { Set-UacDisabled } Invoke-Step -StepName 'Power Configuration' -Fatal { Set-PowerConfig -IsLaptop:$device.IsLaptop } Invoke-Step -StepName 'Adobe Acrobat Reader' { Install-WingetPackageIfMissing ` -PackageId 'Adobe.Acrobat.Reader.64-bit' ` -FriendlyName 'Adobe Acrobat Reader' ` -DetectionPatterns @('Adobe Acrobat Reader', 'Adobe Acrobat \(64-bit\)', 'Acrobat Reader') } Invoke-Step -StepName 'Google Chrome' { Install-WingetPackageIfMissing ` -PackageId 'Google.Chrome' ` -FriendlyName 'Google Chrome' ` -DetectionPatterns @('Google Chrome') } if ($device.Manufacturer -match 'Lenovo') { Invoke-Step -StepName 'Lenovo Vantage' { Install-WingetPackageIfMissing ` -PackageId '9WZDNCRFJ4MV' ` -FriendlyName 'Lenovo Vantage' ` -DetectionPatterns @('Lenovo Vantage') ` -Source 'msstore' } } else { Write-Log "Skipping Lenovo Vantage because device is not Lenovo" 'WARN' Add-StepResult -Step 'Lenovo Vantage' -Status 'SKIPPED' -Message 'Device is not Lenovo' } Invoke-Step -StepName 'Microsoft 365 Apps for business' { Install-OfficeBusiness } Invoke-Step -StepName 'Rename Local User' -Fatal { Rename-SpecifiedLocalUser -CurrentUserName $CurrentLocalUserName -NewUserName $NewLocalUserName } Write-Log "Provisioning run completed" } catch { Write-Log "Provisioning failed: $($_.Exception.Message)" 'ERROR' throw } finally { Write-FinalSummary Stop-Transcript | Out-Null }