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++
}
}
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.
REM Path to the PsExec executable
echo All servers processed.
That completes the mission.