I am trying to get the following information through Powershell on my AD: OS, OS Version, OS Architecture (32 or 64 bit), RAM, and space left on C Drive. I wrote the following script, but it isn’t pulling the computers in the list like I wanted. The 2023ComputerList has all of our domain computers on it, separated by line.

$computers = Get-Content -Path ‘C:\scripts\2023ComputerList.txt’

foreach ($computer in $computers) {
$Info = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -Property Name
$RAM = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -Property TotalPhysicalMemory
$Bios = Get-CimInstance -ClassName Win32_BIOS
$OperatingSystem = Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property Caption, OSArchitecture
$HardDrive = Get-CimInstance -ClassName Win32_LogicalDisk -Filter “DriveType=3”

$OutputObj = New-Object -Type PSObject
$OutputObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Info
$OutputObj | Add-Member -MemberType NoteProperty -Name RAM -Value $RAM
$OutputObj | Add-Member -MemberType NoteProperty -Name BIOS -Value $Bios
$OutputObj | Add-Member -MemberType NoteProperty -Name OperatingSystem -Value $OperatingSystem
$OutputObj | Add-Member -MemberType NoteProperty -Name HardDrive -Value $HardDrive
$OutputObj | Export-Csv ‘C:\scripts\2023PCInventoryList.csv’ -Append -NoTypeInformation
}

Any help would be appreciated!

5 Spice ups

Do you only want it in powershell or do you need the details of the servers (even if not using PS) ?

I would recommend you to use a inventory software like PDQ inventory if it is important for you to have the servers data.

Else you may need to confirm the following

  • are there anything blocking PS scripts from being executed remotely (firewall, AV, local security policies) ?
  • what are the user accounts used to execute the PS scripts, especially if you need to access detailed hardware info and system info ?
  • can that user account that execute the PS scripts be used to run on the various servers ?

Nowhere in your script are you telling Get-CimInstance the remote computer name. You also don’t need to call Get-CimInstance so many times when querying the same class. Finally, creating your object like that is the “old” way. It’s better/easier to create a PSCustomObject.

1 Spice up

You’d be better served with using an Invoke-Command so they can be run in parallel since you’re querying so may different classes. If you we only doing 1, Get-CimInstance does support parallelization.

$Computers = Get-Content -Path 'C:\scripts\2023ComputerList.txt'

Invoke-Command -ComputerName $Computers -ScriptBlock {
    [PSCustomObject]@{
        ComputerName    =   $env:COMPUTERNAME
        RAM             =   Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -Property TotalPhysicalMemory
        BIOS            =   Get-CimInstance -ClassName Win32_BIOS
        OperatingSystem =   Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object -Property Caption, OSArchitecture
        HardDrive       =   Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3"
    }
} | Export-Csv -Path 'C:\scripts\2023PCInventoryList.csv' -NoTypeInformation

Additionally, if you go with Invoking, All of those will be available via Get-ComputerInfo except the disk. It gathers a lot more information so speed may be an issue.

I condensed the code a bit to run a few parameters from Get-ComputerInfo (thank you for that suggestion!), and it looks like this:

$Computers = Get-Content -Path ‘C:\scripts\2023ComputerList.txt’

Invoke-Command -ComputerName $Computers -ScriptBlock {
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
ComputerInfo = Get-ComputerInfo | select CsDNSHostName, WindowsProductName, OSArchitecture, CsTotalPhysicalMemory, CsProcessors
HardDrive = Get-CimInstance -ClassName Win32_LogicalDisk -Filter “DriveType=3”
}
} | Export-Csv -Path ‘C:\scripts\2023PCInventoryList.csv’ -NoTypeInformation

This is what I get when I attempt to run it on our DC.
Invoke-Command : Cannot validate argument on parameter ‘ComputerName’. The argument is null or empty. Provide an
argument that is not null or empty, and then try the command again.
At line:3 char:30

  • Invoke-Command -ComputerName $Computers -ScriptBlock {
  • CategoryInfo : InvalidData: (:slight_smile: [Invoke-Command], ParameterBindingValidationException
  • FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeCommandCommand

Any ideas?

Sounds like your 2023ComputerList.txt file isn’t formatted correctly or contains a blank line or lines. It should look like:

SERVER1
SERVER2
SERVER3

and not

SERVER1

SERVER2

or not

SERVER1
SERVER2

1 Spice up

Try adding these lines just above your ForEach:

Write-Host "Here is the Computers variable:`n$($computers)`n`n`tHere is Computer:`n$($computer)"
Read-Host "Got that?"

The Read-Host will make it stop each time, so this won’t be for Production, of course! Break out with …

If you’re looking at all live PCs in your Domain, why bother managing this list?

An easier way to get a live list of all active PCs in your Domain would be thus:

ForEach ($Computer in (Get-ADComputer -Filter {Enabled -eq $True} | Sort).Name){
# [...]
}

…or use whatever selection criteria you used to build the .txt file.

For your freespace, see if this might be useful:

$Result = ForEach ($PC in (Get-ADComputer -Filter {OperatingSystem -notlike "*Server*" -And Enabled -ne $False}).Name | Sort) {
	Write-Progress -Activity "Getting Disk Free Space on $PC." -Status "Checking $PC" 
# Make sure it's online
	If (Test-Connection $PC -Count 1 -Quiet){
		Write-Host $PC
# Load the $Result variable, format the free space as a number, but rounded and decimal-aligned, for Sorting
		[PSCustomObject] @{
			PC	  = $PC
			FreeSpace = [Math]::Round((Get-Volume C -CIMSession $PC).SizeRemaining / 1GB,2, [System.MidpointRounding]::AwayFromZero)
			' '	  = "GB"
		}  # End PSCustomObject
	}  # End If PC is online
}  # End For Each PC
$Result | Sort-Object FreeSpace

HTH…

For RAM, set $PC to a valid name & see if any of this might be useful:

# RAM Installed
		"`t`t`t`tRAM"
		$RAMArray = Get-WmiObject -Class "Win32_PhysicalMemoryArray" -namespace "Root\CIMV2" -ComputerName $PC
		$AllSlots = Get-WmiObject -Class "Win32_PhysicalMemory" -namespace "Root\CIMV2" -ComputerName $PC
		"`tTotal Installed RAM: " + ((Get-WmiObject -Class "CIM_PhysicalMemory" -ComputerName $PC | 
		 Measure-Object -Property Capacity -Sum).Sum / 1GB) + " GB"
		"`tTotal Number of DIMM Slots: $($RAMArray.MemoryDevices)"
		"`n"  
		Foreach ($DIMM In $AllSlots) # Disclose info for each DIMM
			{
			"Memory Installed: `t$($DIMM.DeviceLocator)`t$($DIMM.Description)"
			"BankLabel:        `t$($DIMM.BankLabel)"
			"Memory Size:      `t$(($DIMM.Capacity / 1GB)) GB"
			"Speed:            `t$($DIMM.Speed)"
			"TotalWidth:       `t$($DIMM.TotalWidth)"
			"FormFactor:       `t$($DIMM.FormFactor)"
			"Manufacturer:     `t$($DIMM.Manufacturer)"
			if ($DIMM.Model) {"Model:            `t$($DIMM.Model)"}  ## This seems blank a lot, so why print it?
			if ($DIMM.Caption -ne $DIMM.Description) {  ## Redundant?  Thanks fer nuttin.
				"Caption:          `t$($DIMM.Caption)"}
			if ($DIMM.Name -ne $DIMM.Description) {  ## Oh, even more redundant?  Lovely.
				"Name:           `t$($DIMM.Name)"}
			"Part Number:      `t$($DIMM.PartNumber)"
			"Serial Number:    `t$($DIMM.SerialNumber)"
			if ($DIMM.Attributes) {"Attributes:    `t$($DIMM.Attributes)"}  ## Usually blank but useful if not.
			"`n"
		} # End ForEach DIMM

# RAM Usage
		"`t`t`t`tRAM Usage"
		$props=@(
		    @{Label="Memory Percentage in Use (%)"; Expression = {"{0:N2}" -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory)*100)/ $_.TotalVisibleMemorySize)}};     
		    @{Label="Available Physical Memory (MB)"; Expression = {[math]::round(($_.FreePhysicalMemory / 1kb), 2)}}; 
		    @{Label="Total Physical Memory (MB)"; Expression = {[math]::round(($_.TotalVisibleMemorySize / 1kb), 2)}}; 
		    @{Label="Available Virtual Memory (MB)"; Expression = {[math]::round(($_.FreeVirtualMemory / 1kb), 2)}}; 
		    @{Label="Total Virtual Memory (MB)"; Expression = {[math]::round(($_.TotalVirtualMemorySize /1kb), 2)}}; 
		)
		$OS | Format-List $props

# Top 10 RAM Consumers
		"`t`t`t`tTop 10 RAM Consumers"
		$props=@(
		    @{Label="Process Name"; Expression = {$_.Name}},
		    @{Label="PID"; Expression={$_.Handle}},
		    @{Label="CommandLine"; Expression = {$_.CommandLine}},    
		    @{Label="Private Memory(mb)";Expression={[math]::round(($_.WorkingSetSize / 1mb), 2)}}
		)
		# Need a variable here to further investigate SvcHost processes
		$Consumers = Get-WmiObject Win32_Process -ComputerName $PC |
		 Sort WorkingSetSize -Descending |
		 Select -First 10
		$Consumers | Format-List $props
		$Consumers | ForEach {If ($_.Name -like "*svchost*"){tasklist /svc /FI "imagename eq svchost.exe" /FI "PID eq $($_.Handle)" /S $PC}}

HTH…

ETA: Set the $OS variable thus:

$OS = Get-WMIObject Win32_OperatingSystem -ComputerName $PC

If you ever want more than just the Name for Info, see if some of these are useful:

Get-WMIObject Win32_ComputerSystem -ComputerName $PC | Format-Table Manufacturer, Model,`
   @{Label='Asset Tag';Expression={(Get-WmiObject win32_SystemEnclosure -ComputerName $PC).SerialNumber}},`
   SystemType, @{Label='Cores';Expression={$_.NumberOfLogicalProcessors}},`
   Domain, @{Label='Joined?';Expression={$_.PartOfDomain}}`
    -Wrap -A

(You don’t need a cmdlet for just Name. $computer (or $PC, for me) is it.)

You might find some useful information in the BIOS by this:

$BIOS = Get-WmiObject Win32_BIOS -ComputerName $PC
		[PSCustomObject]@{
		    'Name' = $($BIOS.Name)
		    'Manuf.' = $BIOS.Manufacturer
		    'Description' = $BIOS.Description
		    'Version' = $BIOS.Version
		    'SMBVersion' = $BIOS.SMBIOSBIOSVersion
		    'Caption' = $BIOS.Caption
		    'SN' = $BIOS.SerialNumber
		    'Release Date' = $($BIOS.ConvertToDateTime($BIOS.releasedate).ToShortDateString())
		    'Status' = $($BIOS.Status)
		}

HTH…

Operating System:

$OS = Get-WMIObject Win32_OperatingSystem -ComputerName $PC
# Windows
		"`r`n`t`t`t`tOperating System"
		$RealBuild = Switch -WildCard ($OS.Version){
			'*19045*'{"22H2"}
			'*19044*'{"21H2"}
			'*19043*'{"21H1"}
			'*19042*'{"20H2"}
			'*19041*'{"2004"}
			'*18363*'{"1909"}
			'*18362*'{"1903"}
			'*17763*'{"1809"}
			'*17134*'{"1803"}
			'*16299*'{"1709"}
			'*15063*'{"1703"}
			'*14393*'{"1607"}
			'*10586*'{"1511"}
			'*10240*'{"1507"}
			Default {" Old"}
			}  # End of Switch

		"OS: $($OS.Caption) $($OS.CSDVersion)  Build: $($OS.BuildNumber) ($($RealBuild)) $($OS.BuildType) $($OS.OSArchitecture)"
		"Free Physical Memory: $([Math]::Round($OS.FreePhysicalMemory / 1MB,3)) MB - $($OS.FreePhysicalMemory) Bytes"
		"Free Space In Paging Files: $([Math]::Round($OS.FreeSpaceInPagingFiles / 1MB,3)) MB - $($OS.FreeSpaceInPagingFiles) Bytes"
		$UpTime = (Get-Date) - [Management.ManagementDateTimeConverter]::ToDateTime($OS.LastBootUpTime)
		"UpTime " + "{0:00} Days {1:00} Hrs {2:00} Min" -f $UpTime.Days,$UpTime.Hours,$UpTime.Minutes

Getting useful info on Local Disks is hard, but this may help:

# Local Disks
		"`t`t`t`tLocal Disks`n"
		$PhysicalDisks = Get-WmiObject -Class Win32_DiskDrive -ComputerName $PC | Sort-Object DeviceID # -Descending
		ForEach ($PhysicalDisk in $PhysicalDisks) {
			$DiskPartitions = Get-WmiObject -ComputerName $PC `
			  -Query "ASSOCIATORS OF {Win32_DiskDrive.DeviceID='$($PhysicalDisk.DeviceID)'} WHERE ASSOCCLASS = Win32_DiskDriveToDiskPartition"
			ForEach ($DiskPartition in $DiskPartitions) {
				$LogicalDisks = (Get-WmiObject -ComputerName $PC `
				  -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID='$($DiskPartition.DeviceID)'} WHERE ASSOCCLASS = Win32_LogicalDiskToPartition")
				ForEach ($LogicalDisk in $LogicalDisks) {
					"Drive $($LogicalDisk.Name)(" + $LogicalDisk.VolumeName + ")`t$($DiskPartition.DeviceID)`tModel: $($PhysicalDisk.Model)"
					 "`tSize: " + [Math]::Round($LogicalDisk.Size / 1GB,2) + " GB " + 
					 "`tFree Space: " + [Math]::Round($LogicalDisk.FreeSpace / 1GB,2) + " GB " + 
					 "`t($([Math]::round((($LogicalDisk.FreeSpace/$LogicalDisk.Size) * 100),2))" + "% Free)" + 
					 "`tS.M.A.R.T. Status: $($PhysicalDisk.Status)"
				}  # End For Each Logical Disk
			}  # End For Each Partition
			ForEach ($x in Get-WmiObject -ComputerName $PC -namespace root\wmi –class MSStorageDriver_FailurePredictStatus){
				If (!(Compare-Object $x.InstanceName.SubString(0,25) $PhysicalDisk.PNPDeviceID.SubString(0,25))){
					If($x.PredictFailure){
					"*************************  S.M.A.R.T. Failure Predicted!`tReason: $($x.Reason)  *************************"}}
			}  # End ForEach Prediction
		}  # End For Each Physical Disk

# Mapped Network Drives
		"`n`t`t`t`tMapped Network Drives`n"
		Get-WMIObject Win32_MappedLogicalDisk -Property "DeviceID, ProviderName" -ComputerName $PC |
		 FT @{Label="Drive`nLetter"; Expression={$_.DeviceID}}, @{Label="Path"; Expression = {$_.ProviderName}}

HTH…

(I only ask that you learn things and answer other questions.)

#runs this script on all machines in domain:
$computers = Get-ADComputer -Filter * | Select-Object -ExpandProperty Name

$results = foreach ($computer in $computers) {
    $Info = Get-CimInstance -ComputerName $computer -ClassName Win32_ComputerSystem | Select-Object -ExpandProperty Name
    $RAM = Get-CimInstance -ComputerName $computer -ClassName Win32_ComputerSystem | Select-Object -ExpandProperty TotalPhysicalMemory
    $Bios = Get-CimInstance -ComputerName $computer -ClassName Win32_BIOS
    $OperatingSystem = Get-CimInstance -ComputerName $computer -ClassName Win32_OperatingSystem | Select-Object -Property Caption, OSArchitecture
    $HardDrive = Get-CimInstance -ComputerName $computer -ClassName Win32_LogicalDisk -Filter "DriveType=3"

    $OutputObj = New-Object -Type PSObject
    $OutputObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Info
    $OutputObj | Add-Member -MemberType NoteProperty -Name RAM -Value $RAM
    $OutputObj | Add-Member -MemberType NoteProperty -Name BIOS -Value $Bios
    $OutputObj | Add-Member -MemberType NoteProperty -Name OperatingSystem -Value $OperatingSystem
    $OutputObj | Add-Member -MemberType NoteProperty -Name HardDrive -Value $HardDrive

    $OutputObj
}

$results | Export-Csv -Path 'C:\scripts\systeminfo.csv' -NoTypeInformation

 

(Just tested it in a lab):

Alternatively you can roll-out Action1 and make an inventory of your with several clicks. :wink: