Files
windows-builder/includes/$OEM$/$$/OEM/scripts/script-helper.ps1
2026-06-02 03:37:09 -07:00

479 lines
15 KiB
PowerShell

# 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 -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
}
}