With the help of a little PowerShell script, you can easily create an inventory of your Windows machines. In this post I demonstrate how you can remotely retrieve available memory, disk space, and computer names of all the Windows servers in your network.

Prerequisites ^

You're going to need a few prerequisites before we get started:

  • PowerShell Remoting enabled and available to you on all Windows Servers
  • Intermediate level PowerShell knowledge

Scaffolding ^

A server inventory script like this can get massive and unwieldy if not properly planned out. I do this via script scaffolding.

Scaffolding is a term used to refer to writing out the framework of a script. I'm not actually writing functional code just yet; I'm just building the "container" to fit it in.

First, I'll need a source where I can pull the server names from. One possibility is to pull the names from an Active Directory, although this isn't necessarily required. Below you can see I'm using the Get-AdComputer to find all servers in the Servers OU.

$serversOuPath = 'OU=Servers,DC=local'
$servers = Get-ADComputer -SearchBase $serversOuPath -Filter * | Select-Object -ExpandProperty Name

Once I have the server names, I can then begin to scaffold out the code querying the name, operating system, memory, and free disk space of multiple servers. A rough framework of how this will happen is shown below. Notice that I've already built a foreach loop and will be creating an object for each server with four properties.

foreach ($server in $servers) {
    $output = [ordered]@{
        'ServerName'         = $server
        'OperatingSystem'    = $null
        'FreeDiskSpace (GB)' = $null
        'Memory (GB)'        = $null
    }
    [pscustomobject]$output
}

Finding the operating system ^

Now I can begin filling in the values for the $output hashtable I've defined in my script scaffold. The first task is getting the operating system of each server. The best way to do that is to use CIM. I know I can pull the operating system name via Get-CimInstance like this:

PS51> (Get-CimInstance -ComputerName <server> -ClassName Win32_OperatingSystem).Caption
Microsoft Windows Server 2019 Datacenter

Knowing how to find the operating system for a single server, I can add that code and replace the ComputerName value with the variable our server will have in our foreach loop.

foreach ($server in $servers) {
    $output = [ordered]@{
        'ServerName'         = $server
        'OperatingSystem'    = (Get-CimInstance -ComputerName $server -ClassName Win32_OperatingSystem).Caption
        'FreeDiskSpace (GB)' = $null
        'Memory (GB)'        = $null
    }
    [pscustomobject]$output
}

Finding free disk space ^

The next value we need to come up with is the amount of free disk space on one server. We'll then expand that out to our script when we're done.

Again, we can use CIM to handle this, querying the Win32_LogicalDisk class this time.

PS51> Get-CimInstance -ComputerName <server> -ClassName Win32_LogicalDisk
Free disk space

Free disk space

This works, but we have a problem. When this report is run, we're going to have a single server per row, and a server can have more than one disk on it. If we leave this as-is, it's going to look bad when the reports runs.

Let's sum up all of the disk space and round it to the nearest GB so we have a birds-eye view of all disks.

PS51> [Math]::Round(((Get-CimInstance -ComputerName <server> -ClassName Win32_LogicalDisk | Measure-Object -Property FreeSpace -Sum).Sum / 1GB),1)
118.5

We can now pull the sum of free space from all disks on a single server. Let's use the same tactic as before and insert this code into our inventory script.

foreach ($server in $servers) {
    $output = [ordered]@{
        'ServerName'         = $server
        'OperatingSystem'    = (Get-CimInstance -ComputerName $server -ClassName Win32_OperatingSystem).Caption
        'FreeDiskSpace (GB)' = [Math]::Round(((Get-CimInstance -ComputerName $server -ClassName Win32_LogicalDisk | Measure-Object -Property FreeSpace -Sum).Sum / 1GB),1)
        'Memory (GB)'        = $n
    }
    [pscustomobject]$output
}

Finding total memory ^

The next attribute to add to the script is total memory. We can, thankfully, use CIM again to grab this!

PS51> Get-CimInstance -ComputerName <server> -ClassName Win32_PhysicalMemory
CIM memory

CIM memory

We have the same problem as last time. The query to find total memory returns objects with properties. We just need a single string value. We can find the actual memory amount via the Capacity property. Once we have this, we can sum up the capacities from each object that's returned.

PS51> (Get-CimInstance -ComputerName <server> -ClassName Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum
3758096384

Notice above that the 3758096384 sure is a big number! That’s because the number that comes out of CIM is in bytes. We need to convert this to gigabytes to get a better representation.

PS51> (Get-CimInstance -ComputerName <server> -ClassName Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum /1GB
3.5

We can now plug this code into the inventory script.

(Get-CimInstance -ComputerName <server> -ClassName Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum /1GB
foreach ($server in $servers) {
    $output = [ordered]@{
        'ServerName'         = $server
        'OperatingSystem'    = (Get-CimInstance -ComputerName $server -ClassName Win32_OperatingSystem).Caption
        'FreeDiskSpace (GB)' = [Math]::Round(((Get-CimInstance -ComputerName $server -ClassName Win32_LogicalDisk | Measure-Object -Property FreeSpace -Sum).Sum / 1GB),1)
        'Memory (GB)'        = (Get-CimInstance -ComputerName $server -ClassName Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum /1GB
    }
    [pscustomobject]$output
}

When the above code runs, you will see an output like below. I just have a single machine to query but when you run this in a larger environment, you will see one server per row with each property populated.

Inventory

Inventory

We can now add a parameter to dynamically send it server names and finish off this script!

param(
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string[]]$Server
    )

    foreach ($server in $servers) {
        $output = [ordered]@{
            'ServerName'         = $server
            'OperatingSystem'    = (Get-CimInstance -ComputerName $server -ClassName Win32_OperatingSystem).Caption
            'FreeDiskSpace (GB)' = [Math]::Round(((Get-CimInstance -ComputerName $server -ClassName Win32_LogicalDisk | Measure-Object -Property FreeSpace -Sum).Sum / 1GB),1)
            'Memory (GB)'        = (Get-CimInstance -ComputerName $server -ClassName Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum /1GB
        }
        [pscustomobject]$output
    }
)

If I save the above code as Get-SrvInv.ps1, I could then execute the script passing in a list of server names retrieved from Active Directory or another data source.

Notice below I'm using a text file of server names as an example.

PS51> .\Get-SrvInv.ps1 -Server (Get-Content -Path 'servers.txt')

Summary ^

Once you have the basics down of building an inventory report like this, you can start to plug in various code snippets to pull the information you need.

Subscribe to 4sysops newsletter!

If you'd like a deeper look into building a script like this, I encourage you to check out the Building a Tool mini-course that covers these concepts and goes into a lot more detail.

+5
avataravataravatar
2 Comments
  1. Steven 2 years ago

    I would recommend a few adjustments for anyone looking to use this in production: 

    • Use a script block to pass to each server verses opening and closing that many ciminstances
    • Use invoke-command on a array of server names to run this in parallel across multiple servers. 
    • Add error checking for unresponsive servers 
    • Add the option to export as CSV.

    The last code sample has two param blocks.

    0

  2. Clayton (Rank: 2)
    2 years ago

    The final block of code should be more like the following. 

    Users might need to enable PSRemotting and have admin credentials to connect to the server/workstation.

    [CmdletBinding()]
    param(
    	[Parameter()]
    	[ValidateNotNullOrEmpty()]
    	[string[]]$Servers
    )
    
    foreach ($server in $servers) {
        $output = [ordered]@{
            'ServerName'         = $server
            'OperatingSystem'    = (Get-CimInstance -ComputerName $server -ClassName Win32_OperatingSystem).Caption
            'FreeDiskSpace (GB)' = [Math]::Round(((Get-CimInstance -ComputerName $server -ClassName Win32_LogicalDisk | Measure-Object -Property FreeSpace -Sum).Sum / 1GB),1)
            'Memory (GB)'        = (Get-CimInstance -ComputerName $server -ClassName Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum /1GB
        }
        [pscustomobject]$output
    }
    +1

Leave a reply

Please enclose code in pre tags

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2021

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account