479 lines
15 KiB
PowerShell
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
|
|
}
|
|
} |