Files
System-Info/System Info (ps1)/Get-SystemSecurityAndHardwareInfo.ps1
2025-10-15 02:37:02 -04:00

316 lines
16 KiB
PowerShell

<#
Get-SystemSecurityAndHardwareInfo.ps1
Gather TPM, Secure Boot, RAM, CPU, drive info and optionally write to a text file.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$false, HelpMessage="Path or filename to write output to as a .txt file. If a relative filename is provided it will be created in the script folder.")]
[string]$OutputFile,
[Parameter(Mandatory=$false, HelpMessage="Enable debug logging to a timestamped debug file in the script folder")]
[switch]$DebugMode
)
# Note about host: If this script is run under Windows PowerShell (5.1) some advanced detection
# (CPU intrinsics via System.Runtime.Intrinsics) will be unavailable. If you want a single
# copy-paste one-liner that automatically runs the script under pwsh (PowerShell 7+) use the
# following pattern (recommended when piping from the web):
#
# iwr -UseBasicParsing "https://git.smartcraft.me/Smartcraft-Media-Tech/System-Info/raw/branch/master/System%20Info%20%28ps1%29/Get-SystemSecurityAndHardwareInfo.ps1" | & pwsh -Command -
#
# This will fetch the script and execute it under pwsh so instruction-set detection works.
function Get-TPMStatus {
# Prefer the Get-Tpm cmdlet when available (gives clear SpecVersion/ManufacturerVersion)
try {
if (Get-Command -Name Get-Tpm -ErrorAction SilentlyContinue) {
$g = Get-Tpm -ErrorAction Stop
$spec = $null
if ($g.SpecVersion) { $spec = ($g.SpecVersion -join ', ') } elseif ($g.SpecVersion -ne $null) { $spec = [string]$g.SpecVersion }
$manVer = $null
try { if ($g.ManufacturerVersion) { $manVer = $g.ManufacturerVersion } } catch { }
# Normalize spec version: make a raw string and extract primary (first) component
$specRaw = $null
$specPrimary = $null
if ($spec) {
if ($spec -is [System.Array]) { $specRaw = ($spec -join ', ') } else { $specRaw = [string]$spec }
$specPrimary = ($specRaw -split ',')[0].Trim()
}
return @{ Installed = ($g.TpmPresent -eq $true); IsEnabled = ($g.TpmReady -eq $true); ManufacturerId = $g.ManufacturerId; SpecVersionPrimary = $specPrimary; SpecVersionRaw = $specRaw; ManufacturerVersion = $manVer }
}
} catch { }
# Try CIM (Win32_Tpm) as a fallback
try {
$tpm = Get-CimInstance -Namespace "root\cimv2\security\microsofttpm" -ClassName Win32_Tpm -ErrorAction Stop
if ($tpm -and $tpm.IsEnabled_InitialValue -ne $null) {
$specRaw = $null
try {
if ($tpm.SpecVersion -is [System.Array]) { $specRaw = ($tpm.SpecVersion -join ', ') } else { $specRaw = [string]$tpm.SpecVersion }
} catch { $specRaw = [string]$tpm.SpecVersion }
$specPrimary = if ($specRaw) { ($specRaw -split ',')[0].Trim() } else { $null }
$manVer = $null
try { if ($tpm.ManufacturerVersion) { $manVer = $tpm.ManufacturerVersion } } catch { }
return @{ Installed = $true; IsEnabled = ($tpm.IsActivated_InitialValue -eq $true) -or ($tpm.IsEnabled_InitialValue -eq $true); ManufacturerId = $tpm.ManufacturerID; SpecVersionPrimary = $specPrimary; SpecVersionRaw = $specRaw; ManufacturerVersion = $manVer }
}
} catch {
# fallback to registry check
}
# Registry fallback (may require elevated privileges)
try {
$reg = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\TPM' -ErrorAction Stop
return @{ Installed = $true; IsEnabled = $true; ManufacturerId = $null; SpecVersion = $null; ManufacturerVersion = $null }
} catch {
return @{ Installed = $false; IsEnabled = $false; ManufacturerId = $null; SpecVersion = $null; ManufacturerVersion = $null }
}
}
function Get-SecureBootStatus {
# Use Confirm-SecureBootUEFI where available (PowerShell 5+)
try {
if (Get-Command -Name Confirm-SecureBootUEFI -ErrorAction SilentlyContinue) {
$sb = Confirm-SecureBootUEFI
return $sb
}
} catch {
# continue to WMI
}
try {
$sbState = (Get-CimInstance -Namespace root\wmi -ClassName MSSmBios_RawSMBiosTables -ErrorAction Stop)
} catch {
# can't determine
}
# If Confirm-SecureBootUEFI unavailable, try checking UEFI SecureBoot in registry (works on modern Windows)
try {
$val = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\State' -Name UEFISecureBootEnabled -ErrorAction Stop
return ($val.UEFISecureBootEnabled -eq 1)
} catch {
return $null
}
}
function Get-RAMInfo {
$totalBytes = (Get-CimInstance -ClassName Win32_ComputerSystem).TotalPhysicalMemory
$gb = [math]::Round($totalBytes / 1GB, 2)
return @{ Bytes = [int64]$totalBytes; GB = $gb }
# Try CIM, fallback to WMIC if CIM fails
try {
$cs = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
$totalBytes = $cs.TotalPhysicalMemory
} catch {
try {
$out = & wmic computersystem get TotalPhysicalMemory /value 2>$null
$match = ($out -split "\r?\n" | Where-Object { $_ -match '^TotalPhysicalMemory=' }) -replace 'TotalPhysicalMemory=' -replace '\r',''
$totalBytes = [int64]($match -join '')
} catch {
$totalBytes = 0
}
}
$gb = if ($totalBytes -gt 0) { [math]::Round($totalBytes / 1GB, 2) } else { 0 }
return @{ Bytes = [int64]$totalBytes; GB = $gb }
}
function Get-CPUInfo {
$cpu = Get-CimInstance -ClassName Win32_Processor | Select-Object -First 1 -Property Name, Manufacturer, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed
$instr = @()
try {
$cpu = Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop | Select-Object -First 1 -Property Name, Manufacturer, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed
} catch {
# fallback to WMIC parsing
try {
$out = & wmic cpu get Name,Manufacturer,NumberOfCores,NumberOfLogicalProcessors,MaxClockSpeed /format:list 2>$null
$props = @{ Name=''; Manufacturer=''; NumberOfCores=0; NumberOfLogicalProcessors=0; MaxClockSpeed=0 }
foreach ($line in $out -split "\r?\n") {
if ($line -match '^Name=(.*)') { $props.Name = $Matches[1].Trim() }
if ($line -match '^Manufacturer=(.*)') { $props.Manufacturer = $Matches[1].Trim() }
if ($line -match '^NumberOfCores=(.*)') { $props.NumberOfCores = [int]$Matches[1].Trim() }
if ($line -match '^NumberOfLogicalProcessors=(.*)') { $props.NumberOfLogicalProcessors = [int]$Matches[1].Trim() }
if ($line -match '^MaxClockSpeed=(.*)') { $props.MaxClockSpeed = [int]$Matches[1].Trim() }
}
$cpu = New-Object psobject -Property $props
} catch {
$cpu = New-Object psobject -Property @{ Name=''; Manufacturer=''; NumberOfCores=0; NumberOfLogicalProcessors=0; MaxClockSpeed=0 }
}
}
# Detect instruction sets using built-in .NET intrinsics when available (PowerShell 7+ / .NET Core+).
# This avoids any third-party/native helpers and uses only system-provided types.
try {
# x86/x64 intrinsics
if ([Type]::GetType("System.Runtime.Intrinsics.X86.Sse") -ne $null) {
if ([System.Runtime.Intrinsics.X86.Sse]::IsSupported) { $instr += 'SSE' }
if ([System.Runtime.Intrinsics.X86.Sse2]::IsSupported) { $instr += 'SSE2' }
if ([System.Runtime.Intrinsics.X86.Sse3]::IsSupported) { $instr += 'SSE3' }
if ([System.Runtime.Intrinsics.X86.Sse41]::IsSupported) { $instr += 'SSE4.1' }
if ([System.Runtime.Intrinsics.X86.Sse42]::IsSupported) { $instr += 'SSE4.2' }
if ([System.Runtime.Intrinsics.X86.Popcnt]::IsSupported) { $instr += 'POPCNT' }
if ([System.Runtime.Intrinsics.X86.Avx]::IsSupported) { $instr += 'AVX' }
if ([System.Runtime.Intrinsics.X86.Avx2]::IsSupported) { $instr += 'AVX2' }
if ([Type]::GetType("System.Runtime.Intrinsics.X86.Bmi1") -ne $null -and [System.Runtime.Intrinsics.X86.Bmi1]::IsSupported) { $instr += 'BMI1' }
if ([Type]::GetType("System.Runtime.Intrinsics.X86.Bmi2") -ne $null -and [System.Runtime.Intrinsics.X86.Bmi2]::IsSupported) { $instr += 'BMI2' }
}
# ARM intrinsics (if running on ARM/ARM64)
if ([Type]::GetType("System.Runtime.Intrinsics.Arm.ArmBase") -ne $null) {
if ([System.Runtime.Intrinsics.Arm.ArmBase]::IsSupported) { $instr += 'ARM_BASE' }
if ([Type]::GetType("System.Runtime.Intrinsics.Arm.AdvSimd") -ne $null -and [System.Runtime.Intrinsics.Arm.AdvSimd]::IsSupported) { $instr += 'AdvSimd' }
if ([Type]::GetType("System.Runtime.Intrinsics.Arm.Crc32") -ne $null -and [System.Runtime.Intrinsics.Arm.Crc32]::IsSupported) { $instr += 'CRC32' }
if ([Type]::GetType("System.Runtime.Intrinsics.Arm.Sha1") -ne $null -and [System.Runtime.Intrinsics.Arm.Sha1]::IsSupported) { $instr += 'SHA1' }
if ([Type]::GetType("System.Runtime.Intrinsics.Arm.Sha256") -ne $null -and [System.Runtime.Intrinsics.Arm.Sha256]::IsSupported) { $instr += 'SHA256' }
}
} catch {
# If intrinsics types are not present (e.g., Windows PowerShell / .NET Framework), fall back to leaving the list empty.
}
return @{ Name = $cpu.Name; Manufacturer = $cpu.Manufacturer; Cores = $cpu.NumberOfCores; LogicalProcessors = $cpu.NumberOfLogicalProcessors; MaxClockMHz = $cpu.MaxClockSpeed; InstructionSets = $instr }
}
function Get-MainDriveInfo {
# Determine system drive (where Windows is installed). $env:SystemDrive is like 'C:' so keep it as-is.
$winDrive = $env:SystemDrive
try {
# Use the drive string directly (e.g. 'C:') in the WMI filter. Avoid trailing colon after the variable to prevent parser errors.
$disk = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$winDrive'" -ErrorAction Stop | Select-Object DeviceID, Size, FreeSpace
} catch {
# fallback to wmic parsing
try {
$device = $winDrive.TrimEnd(':') + ':'
$out = & wmic logicaldisk where "DeviceID='$device'" get DeviceID,Size,FreeSpace /format:list 2>$null
$props = @{ DeviceID=''; Size=0; FreeSpace=0 }
foreach ($line in $out -split "\r?\n") {
if ($line -match '^DeviceID=(.*)') { $props.DeviceID = $Matches[1].Trim() }
if ($line -match '^Size=(.*)') { $props.Size = [int64]($Matches[1].Trim()) }
if ($line -match '^FreeSpace=(.*)') { $props.FreeSpace = [int64]($Matches[1].Trim()) }
}
if ($props.DeviceID) {
$sizeGB = if ($props.Size -gt 0) { [math]::Round($props.Size / 1GB, 2) } else { 0 }
return @{ DeviceID = $props.DeviceID; SizeBytes = [int64]$props.Size; SizeGB = $sizeGB; FreeBytes = [int64]$props.FreeSpace }
}
} catch {
# final fallback
}
}
try {
if ($disk) {
$sizeGB = if ($disk.Size) { [math]::Round($disk.Size / 1GB, 2) } else { 0 }
return @{ DeviceID = $disk.DeviceID; SizeBytes = [int64]$disk.Size; SizeGB = $sizeGB; FreeBytes = [int64]$disk.FreeSpace }
}
} catch { }
return $null
}
# Build output
$runtimeInfo = [ordered]@{}
$runtimeInfo.PSVersion = $PSVersionTable.PSVersion.ToString()
try { $runtimeInfo.Framework = [System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription } catch { $runtimeInfo.Framework = [string]([Environment]::Version) }
$result = [ordered]@{}
$result.Runtime = $runtimeInfo
$result.TPM = Get-TPMStatus
$result.SecureBoot = Get-SecureBootStatus
$result.RAM = Get-RAMInfo
$result.CPU = Get-CPUInfo
$result.MainDrive = Get-MainDriveInfo
function Format-ResultText($res) {
$lines = @()
# Format date as MM-DD-YYYY and time as 12-hour with AM/PM
$dt = Get-Date
$lines += "System Hardware & Security Report - $($dt.ToString('MM-dd-yyyy hh:mm tt'))"
$lines += ""
$lines += "TPM Installed: $($res.TPM.Installed)"
$lines += "TPM Enabled/Activated: $($res.TPM.IsEnabled)"
if ($res.TPM.ManufacturerId) { $lines += "TPM ManufacturerId: $($res.TPM.ManufacturerId)" }
$manVer = if ($res.TPM.ManufacturerVersion) { $res.TPM.ManufacturerVersion } else { 'Not detected' }
$specPrimary = if ($res.TPM.SpecVersionPrimary) { $res.TPM.SpecVersionPrimary } else { 'Not detected' }
$specRaw = if ($res.TPM.SpecVersionRaw) { $res.TPM.SpecVersionRaw } else { 'Not detected' }
$lines += "TPM ManufacturerVersion: $manVer"
$lines += "TPM SpecVersion (primary): $specPrimary"
$lines += "TPM SpecVersion (raw): $specRaw"
$lines += ""
$sb = $res.SecureBoot
if ($sb -eq $null) { $lines += "Secure Boot: Unknown (insufficient privileges or unsupported)" } else { $lines += "Secure Boot Enabled: $sb" }
$lines += ""
$lines += "Installed RAM: $($res.RAM.GB) GB ($([string]::Format('{0:N0}', $res.RAM.Bytes)) bytes)"
$lines += ""
$lines += "CPU: $($res.CPU.Name)"
$lines += "CPU Manufacturer: $($res.CPU.Manufacturer)"
$lines += "CPU Cores: $($res.CPU.Cores) Logical Processors: $($res.CPU.LogicalProcessors) MaxClockMHz: $($res.CPU.MaxClockMHz)"
if ($res.CPU.InstructionSets.Count -gt 0) {
$lines += "CPU Instruction Sets: $($res.CPU.InstructionSets -join ', ')"
} else {
$lines += "CPU Instruction Sets: Not available on this runtime. Try running under PowerShell 7+ (pwsh) for full detection."
}
$lines += ""
$lines += "Runtime: PowerShell $($res.Runtime.PSVersion) $($res.Runtime.Framework)"
$lines += ""
if ($res.MainDrive) {
$lines += "Main Drive ($($res.MainDrive.DeviceID)) Size: $($res.MainDrive.SizeGB) GB ($([string]::Format('{0:N0}', $res.MainDrive.SizeBytes)) bytes) Free: $([math]::Round($res.MainDrive.FreeBytes/1GB,2)) GB"
} else {
$lines += "Main Drive: Unknown"
}
return $lines -join "`n"
}
$outText = Format-ResultText -res $result
# Output to console
Write-Host $outText
# Output to file if requested via -OutputFile
if ($OutputFile) {
try {
$scriptDir = Split-Path -Parent $PSCommandPath
# If the provided path is rooted, use it; otherwise combine with script directory.
if ([System.IO.Path]::IsPathRooted($OutputFile)) {
$outPath = [System.IO.Path]::GetFullPath($OutputFile)
} else {
$outPath = [System.IO.Path]::GetFullPath((Join-Path $scriptDir $OutputFile))
}
# Ensure .txt extension is present
if ([System.IO.Path]::GetExtension($outPath) -eq '') {
$outPath = "$outPath.txt"
}
$outText | Out-File -FilePath $outPath -Encoding UTF8 -Force
Write-Host "Report written to: $outPath"
} catch {
Write-Warning "Failed to write to file: $_"
if ($DebugMode) {
$scriptDir = Split-Path -Parent $PSCommandPath
$dbgFile = Join-Path $scriptDir ("debug_{0:yyyyMMdd_HHmmss}.log" -f (Get-Date))
$err = $_ | Out-String
$debugInfo = @()
$debugInfo += "Timestamp: $(Get-Date -Format o)"
$debugInfo += "BoundParameters: $($PSBoundParameters | Out-String)"
$debugInfo += "Runtime: $($PSVersionTable.PSVersion) $((try { [System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription } catch { [Environment]::Version }))"
$debugInfo += "Error: $err"
$debugInfo | Out-File -FilePath $dbgFile -Encoding UTF8 -Force
Write-Host "Debug log written to: $dbgFile"
}
}
}
# Wrap entire execution in a global try/catch when DebugMode is requested to capture unexpected failures
if ($DebugMode) {
try {
# Already executed main logic above; place-holder to be consistent with debug handling.
} catch {
$scriptDir = Split-Path -Parent $PSCommandPath
$dbgFile = Join-Path $scriptDir ("debug_{0:yyyyMMdd_HHmmss}.log" -f (Get-Date))
$err = $_ | Out-String
$debugInfo = @()
$debugInfo += "Timestamp: $(Get-Date -Format o)"
$debugInfo += "BoundParameters: $($PSBoundParameters | Out-String)"
$debugInfo += "Runtime: $($PSVersionTable.PSVersion) $((try { [System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription } catch { [Environment]::Version }))"
$debugInfo += "Error: $err"
$debugInfo | Out-File -FilePath $dbgFile -Encoding UTF8 -Force
Write-Host "Fatal error — debug log written to: $dbgFile"
exit 1
}
}