Powershell : CPU Capabilities and Assessment


I was asked the other day if I could run an order to script to collect details about the CPU data on a random selection of 100 random servers, which was interesting as it turned into update the script to make it more efficient.

👀 The current solution

I took a look at the script and it involved running a very old visual basic scripting (VBS) - I found a couple of problems with this script for example it presented the user with would pop up dialogue that was actually empty meaning, you have to click an OK button to an empty message.

✋ Scripting from the last century

The script, however, did collect all the data, but it was not very efficient shall we say, it seemed to me like It was coded back in the 1990s and they never updated.

🦀 Powershell should be your weapon of choice

The solution to this should be Powershell, I try to avoid remotely running VBS files to almost certainly require the cscript.exe - this is especially required if you need to run scripts remotely - not very user friendly.

The point here is if you have a SIEM, and then you attempt to run a VBS script on a bunch of servers with an elevated command prompt fully expect that to fire quite a few alerts into your security team, specially, is the target surfers are sensitive servers like the domain controllers.

👾 The requirement

I think I touched on this earlier, but it would appear. We need to run this on a random selection of 100 servers with a script that is really not particularly nice to run and you need to run it with elevated command prompt credentials, so the requirements is pretty clear run this visual basic code script on some random servers for the output.

🗣️ Assess the actual output

Lets check the output from this VBS file and then see and assess our options, so below is the output:

Capabilities:
  Hyper-Threading Technology: Enabled
  Multi-core: Yes
  Multi-processor: Yes

Hardware capability and its availability to applications:
  System wide availability: 2 physical processors, 12 cores per processor, 24 logical processors
  Multi-core capability: 12 cores per package
  HT capability: 2 logical processors per core

All cores in the system are enabled for this application.

Affinity mask, Initial APIC ID, and sub-IDs:  AffinityMask = 1; Initial APIC = 0; Physical ID = Proc 1, Core ID = 0, SMT ID = 0
  AffinityMask = 2; Initial APIC = 1; Physical ID = Proc 1, Core ID = 1, SMT ID = 0
  AffinityMask = 4; Initial APIC = 2; Physical ID = Proc 1, Core ID = 2, SMT ID = 0
  AffinityMask = 8; Initial APIC = 3; Physical ID = Proc 1, Core ID = 3, SMT ID = 0
  AffinityMask = 32; Initial APIC = 5; Physical ID = Proc 1, Core ID = 5, SMT ID = 0
  AffinityMask = 64; Initial APIC = 6; Physical ID = Proc 1, Core ID = 6, SMT ID = 0
  AffinityMask = 128; Initial APIC = 7; Physical ID = Proc 1, Core ID = 7, SMT ID = 0
  AffinityMask = 256; Initial APIC = 8; Physical ID = Proc 1, Core ID = 8, SMT ID = 0
  AffinityMask = 512; Initial APIC = 9; Physical ID = Proc 1, Core ID = 9, SMT ID = 0
  AffinityMask = 1024; Initial APIC = 10; Physical ID = Proc 1, Core ID = 10, SMT ID = 0
  AffinityMask = 2048; Initial APIC = 11; Physical ID = Proc 1, Core ID = 11, SMT ID = 0

This VBS script then reports on the CPU capabilities then reports on what the application can see and then reports all the infinity masks available on the CPU, well that's simple enough to re-write in Powershell

Script : Hyperthread.ps1

This script will shows the details on the screen, but this is to confirm that the data is valid and intact, this covers the moving the code to a Powershell script:

# Retrieve CPU information using Get-CimInstance
$cpu = Get-CimInstance -ClassName Win32_Processor

# Initialize variables
$hyperThreadingEnabled = $false
$multiCore = $false
$multiProcessor = $false

# Determine Hyper-Threading, Multi-Core, and Multi-Processor capabilities
$coresPerProcessor = $cpu[0].NumberOfCores
$logicalProcessors = $cpu[0].ThreadCount
$physicalProcessors = $cpu.Count

$hyperThreadingEnabled = ($logicalProcessors -gt $coresPerProcessor)
$multiCore = ($coresPerProcessor -gt 1)
$multiProcessor = ($physicalProcessors -gt 1)

# System-wide capabilities
$totalCores = $coresPerProcessor * $physicalProcessors
$totalLogicalProcessors = $logicalProcessors

# HT Capability (Assuming HT capability implies multiple threads per core)
$htCapability = $logicalProcessors / $coresPerProcessor

# Determine status for output
$htStatus = if ($hyperThreadingEnabled) { 'Enabled' } else { 'Disabled' }
$multiCoreStatus = if ($multiCore) { 'Yes' } else { 'No' }
$multiProcessorStatus = if ($multiProcessor) { 'Yes' } else { 'No' }

# Print CPU capabilities
Write-Output "Capabilities:"
Write-Output "  Hyper-Threading Technology: $htStatus"
Write-Output "  Multi-core: $multiCoreStatus"
Write-Output "  Multi-processor: $multiProcessorStatus"
Write-Output ""
Write-Output "Hardware capability and its availability to applications:"
Write-Output "  System wide availability: $physicalProcessors physical processors, $coresPerProcessor cores per processor, $totalLogicalProcessors logical processors"
Write-Output "  Multi-core capability: $coresPerProcessor cores per package"
Write-Output "  HT capability: $htCapability logical processors per core"
Write-Output ""
Write-Output "All cores in the system are enabled for this application."

# Display affinity mask, Initial APIC ID, and sub-IDs
$i = 0
$cpu | ForEach-Object {
    foreach ($core in 0..($coresPerProcessor - 1)) {
        Write-Output "  AffinityMask = $([math]::Pow(2, $i)); Initial APIC = $core; Physical ID = $($cpu[0].SocketDesignation), Core ID = $core, SMT ID = 0"
        $i++
    }
}

👣 Add in the log file

That will then reproduce the data from the old script and quicker as well, however this only partially completes the requirements, we need to store that in a text file so for that lets define some variables that we can add to the above script, these will got at the top of the script:

# Define the path for the log file
$computerName = $env:COMPUTERNAME
$logFilePath = "c:\temp\$computerName`_HT_Report.txt"


Then at the bottom of that script add these lines of code which will actually write the file locally to the server under the folder in this example C:\Temp\

# Save the output to the log file
$output | Out-File -FilePath $logFilePath -Encoding UTF8

# Confirm the log file was created
Write-Output "Log file saved to $logFilePath"

🧠 Write Log files to SMB share

This is still not ideal, we need to run this on 100 servers and I would rather not manually visit all 100 servers or write another script to get the log files from them, so we need to amend the variables in the script we added earlier, just a single line that defines the $logFilePath and we need to point that at a SMB network share:

$logFilePath = "\\smbshare\cpu\$computerName`_HT_Report.txt"

That will then put the log files in one convincent location as you can see below:


Remote running on servers

This will take a couple of steps which will include moving that script and the psexec executable to a network location for this example I have used \\smbshare\cpu\ - then you will also require a servers.txt file on the servers.txt when you will run this command, and here the servers will be listed one per line.

Then the process is simple psexec a copy the file command to the server locally, then psexec a Powershell run command from that server, so for each server it will copy the file locally and then execute it.

Execution Script : ExecuteCPU.bat

@echo off
setlocal

REM Path to the PsExec executable
set PSEXEC_PATH="\\smbshare\cpu\Lee\PsExec.exe"

REM Path to the PowerShell script
set SCRIPT_PATH=\\smbshare\cpu\Hyperthread-SMBSave.ps1

REM Temporary path on the remote server
set TEMP_PATH=C:\temp\Hyperthread-SMBSave.ps1

REM Path to the servers list
set SERVERS_LIST=servers.txt

REM Loop through each server in the servers list
for /f "tokens=*" %%i in (%SERVERS_LIST%) do (
    echo Processing server %%i

    REM Copy the PowerShell script to the remote server
    %PSEXEC_PATH% \\%%i -s -i cmd /c "copy %SCRIPT_PATH% %TEMP_PATH%"

    REM Run the PowerShell script on the remote server
    %PSEXEC_PATH% \\%%i -s -i "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File %TEMP_PATH%
)

echo All servers processed.
endlocal

That completes the mission.
Previous Post Next Post

نموذج الاتصال