# This script contains functions and utilities for use in other scripts. # Get the Windows drive $windowsDrive = Split-Path $env:SystemRoot -Qualifier # Logpath $logPath = "$windowsDrive\Windows\Setup\PrePostInstall\Logs" # Create log directory if it doesn't exist if (-not (Test-Path -Path $logPath)) { New-Item -ItemType Directory -Path $logPath | Out-Null } # ========================= # Network helpers # ========================= function Get-ContentFromUrl { param([string]$url) (Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 15 -ErrorAction Stop).Content } # ========================= # Lazy CIM cache # ========================= $script:CimCache = @{} function Get-CachedCimInstance { param([Parameter(Mandatory)][string]$ClassName) if (-not $script:CimCache.ContainsKey($ClassName)) { $script:CimCache[$ClassName] = Get-CimInstance -ClassName $ClassName } return $script:CimCache[$ClassName] } # ========================= # System information # ========================= function Get-SystemArchitecture { $os = Get-CachedCimInstance Win32_OperatingSystem if ($os.OSArchitecture -match "64") { "64-bit" } else { "32-bit" } } function Get-NativeArchitecture { # PROCESSOR_ARCHITEW6432 is set when a 32-bit process runs on 64-bit Windows $a = $env:PROCESSOR_ARCHITEW6432 if (-not $a) { $a = $env:PROCESSOR_ARCHITECTURE } switch ($a.ToUpper()) { "AMD64" { "x64" } "ARM64" { "arm64" } "X86" { "x86" } default { $a.ToLower() } } } function Get-SystemInfo { $computerSystem = Get-CachedCimInstance Win32_ComputerSystem $baseboard = Get-CachedCimInstance Win32_BaseBoard $architecture = Get-SystemArchitecture return @{ Manufacturer = $computerSystem.Manufacturer BaseBoardManufacturer = $baseboard.Manufacturer Model = $computerSystem.Model SystemType = $computerSystem.SystemType TotalPhysicalMemoryGB = [math]::Round($computerSystem.TotalPhysicalMemory / 1GB, 2) Architecture = $architecture deviceIsMac = ($computerSystem.Manufacturer -match "Apple" -or $baseboard.Manufacturer -match "Apple") } } # ========================= # Windows Version Functions # ========================= function Get-WindowsBuildNumber { [int](Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentBuildNumber } function Get-WindowsEdition { $edition = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").EditionID switch -Wildcard ($edition) { "*Professional*" { if ($edition -like "*N*") { "Pro N" } elseif ($edition -like "*Workstation*") { "Pro for Workstations" } else { "Pro" } } "*Enterprise*" { if ($edition -like "*N*") { "Enterprise N" } else { "Enterprise" } } "*Education*" { if ($edition -like "*N*") { "Education N" } else { "Education" } } "*Home*" { if ($edition -like "*N*") { "Home N" } else { "Home" } } "*Core*" { "Home" } "*ProfessionalCountrySpecific*" { "China" } "*ProfessionalSingleLanguage*" { "Single Language" } "*Server*" { $edition } default { $edition } } } function Get-WindowsCodeRelease { $cv = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" if ($cv.DisplayVersion) { $cv.DisplayVersion } elseif ($cv.ReleaseId) { $cv.ReleaseId } else { "$($cv.CurrentBuild).$($cv.UBR)" } } # ========================= # Product Keys # ========================= function Get-OEMKey { try { $svc = Get-CimInstance SoftwareLicensingService if ($svc.OA3xOriginalProductKey -and $svc.OA3xOriginalProductKey.Trim()) { $svc.OA3xOriginalProductKey } } catch {} } function Get-InstalledKey { try { $regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" $value = (Get-ItemProperty -Path $regPath -Name DigitalProductId).DigitalProductId $keyOffset = 52 $chars = "BCDFGHJKMPQRTVWXY2346789" $productKey = "" for ($i = 24; $i -ge 0; $i--) { $cur = 0 for ($j = 14; $j -ge 0; $j--) { $cur = $cur * 256 $cur = $value[$j + $keyOffset] + $cur $value[$j + $keyOffset] = [math]::Floor($cur / 24) $cur = $cur % 24 } $productKey = $chars[$cur] + $productKey if (($i % 5) -eq 0 -and $i -ne 0) { $productKey = "-" + $productKey } } $productKey } catch {} } function Get-ProductKey { if ($k = Get-OEMKey) { return $k } if ($k = Get-InstalledKey) { return $k } "No product key found" } # ========================= # Licensing # ========================= function Get-LicenseStatusCode { $SWLP = Get-CachedCimInstance SoftwareLicensingProduct $product = $SWLP | Where-Object { $_.Name -like "Windows*" -and $_.PartialProductKey -and $_.LicenseStatus -ne $null } | Sort-Object LicenseStatus -Descending | Select-Object -First 1 $product.LicenseStatus } function Get-LicenseType { $SWLP = Get-CachedCimInstance SoftwareLicensingProduct $os = Get-CachedCimInstance Win32_OperatingSystem if ($os.Caption -match "Evaluation") { return "Evaluation" } $product = $SWLP | Where-Object { $_.PartialProductKey -and $_.LicenseStatus -eq 1 -and $_.Name -like "Windows*" } | Select-Object -First 1 if (-not $product) { return "Unlicensed" } $desc = $product.Description if ($desc -match "VOLUME_KMS") { return "Volume (KMS)" } if ($desc -match "VOLUME_MAK") { return "Volume (MAK)" } if ($desc -match "OEM_DM") { return "OEM (Digital Marker)" } if ($desc -match "OEM") { return "OEM" } if ($desc -match "RETAIL") { return "Retail" } "Unknown" } function Get-WindowsVersionDetails { $cv = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" $licenseStatus = Get-LicenseStatusCode return @{ Edition = Get-WindowsEdition CodeRelease = Get-WindowsCodeRelease ProductName = $cv.ProductName DisplayVersion = $cv.DisplayVersion ReleaseId = $cv.ReleaseId CurrentBuild = $cv.CurrentBuild UBR = $cv.UBR InstallationType = $cv.InstallationType LicenseStatus = switch ($licenseStatus) { 0 { "Unlicensed" } 1 { "Licensed" } 2 { "Out of Box Grace Period" } 3 { "Out of Tolerance Grace Period" } 4 { "Non-Genuine Grace Period" } 5 { "Notification" } 6 { "Extended Grace" } default { "Unknown" } } LicenseType = Get-LicenseType } } # ========================= # Build helpers # ========================= function Invoke-CommandsForBuildVersion { param( [int]$targetBuildNumber, [scriptblock]$commands, [ValidateSet("eq","ne","gt","lt","ge","le")] [string]$Comparison = "ge", # default: greater or equal [switch]$Silent # if set, suppress Write-Host output ) $currentBuildNumber = Get-WindowsBuildNumber $runCommand = switch ($Comparison) { "eq" { $currentBuildNumber -eq $targetBuildNumber } "ne" { $currentBuildNumber -ne $targetBuildNumber } "gt" { $currentBuildNumber -gt $targetBuildNumber } "lt" { $currentBuildNumber -lt $targetBuildNumber } "ge" { $currentBuildNumber -ge $targetBuildNumber } "le" { $currentBuildNumber -le $targetBuildNumber } } if ($runCommand) { if (-not $Silent) { Write-Host "Running commands for build $currentBuildNumber ($Comparison $targetBuildNumber)..." } & $commands } } # ========================= # Privilege helpers # ========================= function Test-Administrator { $p = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } ## this function allows PowerShell to take ownership of the Scheduled Tasks registry key from TrustedInstaller. Based on Jose Espitia's script. function Enable-Privilege { param( [ValidateSet( "SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeBackupPrivilege", "SeChangeNotifyPrivilege", "SeCreateGlobalPrivilege", "SeCreatePagefilePrivilege", "SeCreatePermanentPrivilege", "SeCreateSymbolicLinkPrivilege", "SeCreateTokenPrivilege", "SeDebugPrivilege", "SeEnableDelegationPrivilege", "SeImpersonatePrivilege", "SeIncreaseBasePriorityPrivilege", "SeIncreaseQuotaPrivilege", "SeIncreaseWorkingSetPrivilege", "SeLoadDriverPrivilege", "SeLockMemoryPrivilege", "SeMachineAccountPrivilege", "SeManageVolumePrivilege", "SeProfileSingleProcessPrivilege", "SeRelabelPrivilege", "SeRemoteShutdownPrivilege", "SeRestorePrivilege", "SeSecurityPrivilege", "SeShutdownPrivilege", "SeSyncAgentPrivilege", "SeSystemEnvironmentPrivilege", "SeSystemProfilePrivilege", "SeSystemtimePrivilege", "SeTakeOwnershipPrivilege", "SeTcbPrivilege", "SeTimeZonePrivilege", "SeTrustedCredManAccessPrivilege", "SeUndockPrivilege", "SeUnsolicitedInputPrivilege")] $Privilege, ## The process on which to adjust the privilege. Defaults to the current process. $ProcessId = $pid, ## Switch to disable the privilege, rather than enable it. [Switch] $Disable ) $definition = @' using System; using System.Runtime.InteropServices; public class AdjPriv { [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen); [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); [DllImport("advapi32.dll", SetLastError = true)] internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid); [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct TokPriv1Luid { public int Count; public long Luid; public int Attr; } internal const int SE_PRIVILEGE_ENABLED = 0x00000002; internal const int SE_PRIVILEGE_DISABLED = 0x00000000; internal const int TOKEN_QUERY = 0x00000008; internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; public static bool EnablePrivilege(long processHandle, string privilege, bool disable) { bool retVal; TokPriv1Luid tp; IntPtr hproc = new IntPtr(processHandle); IntPtr htok = IntPtr.Zero; retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); tp.Count = 1; tp.Luid = 0; if(disable) { tp.Attr = SE_PRIVILEGE_DISABLED; } else { tp.Attr = SE_PRIVILEGE_ENABLED; } retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid); retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); return retVal; } } '@ $processHandle = (Get-Process -id $ProcessId).Handle $type = Add-Type $definition -PassThru $type[0]::EnablePrivilege($processHandle, $Privilege, $Disable) } # ========================= # Registry helper # ========================= function Set-RegistryValue { param([string]$KeyPath,[string]$ValueName,[string]$ValueType,[string]$ValueData,[string]$Description="") try { $result = & reg add $KeyPath /v $ValueName /t $ValueType /d $ValueData /f 2>&1 if ($LASTEXITCODE -ne 0 -and $Description) { Write-Host "Warning: Could not set registry value for $Description - $result" } } catch { if ($Description) { Write-Host "Warning: Registry operation failed for $Description - $($_.Exception.Message)" } } } # ========================= # .NET installer # ========================= function Install-DotNetPackage { param([array]$Installers,[string]$NamePattern,[string]$DisplayName) $systemInfo = Get-SystemInfo $installer = $null if ($systemInfo.Architecture -eq "64-bit") { $installer = $Installers | Where-Object { $_.Name -match "$NamePattern.*win-x64" } | Select-Object -First 1 } if (-not $installer) { $installer = $Installers | Where-Object { $_.Name -match "$NamePattern.*win-x86" } | Select-Object -First 1 } if ($installer) { Write-Host "Installing $DisplayName ($($installer.Name))..." Start-Process $installer.FullName -ArgumentList "/install","/quiet","/norestart" -Wait } else { Write-Host "$DisplayName installer not found." } } # ========================= # Cursor installer # ========================= function Install-CursorScheme { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$SchemeName, [Parameter(Mandatory)] [string]$CursorDir, # e.g. C:\Windows\Cursors\nikocursor [Parameter(Mandatory)] [hashtable]$Files, # INF-style mapping [switch]$SetActive ) $CursorReg = "HKCU:\Control Panel\Cursors" $CursorSchemeReg = "HKCU:\Control Panel\Cursors\Schemes" New-Item -Path $CursorReg -Force | Out-Null New-Item -Path $CursorSchemeReg -Force | Out-Null # # INF → Windows logical name mapping # $RoleMap = @{ Arrow = "pointer" Help = "help" AppStarting = "working" Wait = "busy" Crosshair = "precision" IBeam = "text" NWPen = "hand" No = "unavailable" SizeNS = "vert" SizeWE = "horz" SizeNWSE = "dgn1" SizeNESW = "dgn2" SizeAll = "move" UpArrow = "alternate" Hand = "link" Person = "person" Pin = "pin" } # # Build canonical 17-slot ordered list # $CursorMap = [ordered]@{} foreach ($winRole in @( "Arrow", "Help", "AppStarting", "Wait", "Crosshair", "IBeam", "NWPen", "No", "SizeNS", "SizeWE", "SizeNWSE", "SizeNESW", "SizeAll", "UpArrow", "Hand", "Pin", "Person" )) { $infKey = $RoleMap[$winRole] if ($Files.ContainsKey($infKey)) { $CursorMap[$winRole] = Join-Path $CursorDir $Files[$infKey] } else { # Fallback: Arrow cursor $CursorMap[$winRole] = Join-Path $CursorDir $Files["pointer"] } } # # 1. Write scheme (Scheme.Reg equivalent) # $SchemeValue = ($CursorMap.Values) -join "," Set-ItemProperty -Path $CursorSchemeReg -Name $SchemeName -Value $SchemeValue -Type String # # 2. Optionally apply (Wreg equivalent) # if ($SetActive) { foreach ($name in $CursorMap.Keys) { Set-ItemProperty -Path $CursorReg -Name $name -Value $CursorMap[$name] -Type String } # # Force reload (SPI_SETCURSORS) # Add-Type @" using System; using System.Runtime.InteropServices; public static class Native { [DllImport("user32.dll")] public static extern bool SystemParametersInfo( int action, int param, IntPtr vparam, int winini); } "@ -ErrorAction SilentlyContinue [Native]::SystemParametersInfo(0x57, 0, [IntPtr]::Zero, 0) # Legacy parity (optional but harmless) RUNDLL32.EXE USER32.DLL, UpdatePerUserSystemParameters , 1 , True | Out-Null } }