Quick Links

PowerShell is used by many server administrators. Naturally, one of the most used tasks is the ability to build scripts and functions to inventory your servers and understand what your environment has.

Though there are many ways to accomplish this, with varying levels of complexity, we are going to build a fairly simple but effect Windows Server Inventory Report within this article.

Prerequisites

This article will be hands-on. If you intend to follow along, please be sure you have the following prerequisites in place first:

  • Working on an Active Directory (AD) domain-joined Windows 10 PC
  • Have the ActiveDirectory PowerShell module installed from the RSAT toolkit.
  • Have permission to query AD computer accounts
  • Can run remote WMI/CIM queries against remote computers
  • Have PowerShell Remoting available on remote computers

Retrieving Servers

Foundational to the script we are building are the servers themselves. You could individually write them out in a text file that is read in, or in an array within the script itself but using PowerShell we can do one better. To make the script more dynamic and not require us to modify it any time a new server is added, we can use Active Directory (AD) to pull the list of computer objects in a given organizational unit (OU).

Below we are utilizing the

        ActiveDirectory
    

module, available in the RSAT toolkit, to query the

        Servers
    

OU and retrieve all of the computer objects there via

        Get-ADComputer
    

.

        Import-Module ActiveDirectory

$OU = 'OU=Servers,DC=domain,DC=local'

$Params = @{
    "SearchBase" = $OU
    "Filter" = '*'
}

$Servers = Get-ADComputer @Params

At this point, we could have filtered out just the name property to populate the $servers variable, but often it is very useful to have the entire returned object to utilize later.

Determining the Data to Collect

Now that we have our servers, we need to figure out what exactly should we collect from each server. One reason that it can be important to keep the full AD object is to combine that data with data direct from the server itself to gain a larger picture of your environment.

In practice what does something like this look like? Let's list out some of the properties that would be very useful to know.

Server Values

  • Server Host Name
  • Free Disk Space
  • Memory
  • Network Connections

AD Values

  • Password Last Set
  • Last Logon
  • DNS Host Name

Retrieving Server Information

How do we go about collecting this information on our list of returned servers? Since we have a list of servers, we will have to iterate over the $Servers object and query. Starting with a simple Foreach-Object loop below, we can create a custom object to hold our values.

        $Servers | Foreach-Object {
    [PSCustomObject]@{
        "ServerHostName" = $_.Name
        "Description" = $_.Description
        "FreeDiskSpace" = $Null
        "TotalMemory" = $Null
        "NetworkConnections" = $Null
        "PasswordLastSet" = $_.pwdLastSet
        "LastLogon" = $_.lastLogon
        "DNSHostName" = $_.DNSHostName
        "CreationDate" = $_.WhenCreated
    }
}

As you can tell, by saving the full object from Active Directory when we first retrieved the computers, lets us populate a wide range of information. Unfortunately, this is not all of the information that we need.

To get the information from each server, we will utilize a familiar interface to many server administrators, which is the Windows Management Instrumentation (WMI) interface. You may notice that the cmdlets used below are from the Common Information Model (CIM) interface, of which WMI is Microsoft's implementation of this standard.

Get the Free Disk Space

Using the available WMI class of Win32_LogicalDisk, we can get all of the available disks and their free space. When we first run the command, Get-CimInstance -ClassName Win32_LogicalDisk, you might notice that it's not exactly readable in its default output.

The second problem here is that we have more than one drive being returned. I would like to know about each of those drives and how much free space is available in GBs. Let's modify the code to do some transformations and make it better.

        $Disks = Get-CimInstance -ClassName Win32_LogicalDisk

$DisksResult = $Disks | Foreach-Object {
    [PSCustomObject]@{
        "Drive" = $_.DeviceID
        "FreeSpace" = [Math]::Round(($_.FreeSpace / 1GB),2)
    }
}

$DisksResult

After we run the commands, our output is much cleaner and can now be used in our script.

But what if we wanted to alert on a low disk space condition? It would be nice to expand this just slightly to set a flag on each drive that meets that condition. Comparing the free space to the total available space, we can see if it's under 10% or 10 GB. The reason for the -or condition, is that on very large disks, 10% may still be very generous so setting an absolute limit helps.

        $Disks = Get-CimInstance -ClassName Win32_LogicalDisk

$DisksResult = $Disks | Foreach-Object {
  $FreeSpace = [Math]::Round(($_.FreeSpace / 1GB),2)
  $TotalSpace = [Math]::Round(($_.Size / 1GB),2)

  If ( ($FreeSpace / $TotalSpace -LT 0.10) -Or $FreeSpace -LT 10 ) {
    $LowDiskSpace = $True
  } Else {
    $LowDiskSpace = $False
  }

    [PSCustomObject]@{
        "Drive" = $_.DeviceID
    "FreeSpace" = $FreeSpace
    "LowDiskSpace" = $LowDiskSpace
    }
}

$DisksResult

As you can tell now, we have a great set of information to be saved with our servers.

How%20to%20Build%20a%20Windows%20Server%20Inventory%20Report%20for/Untitled%202.png

Get the Memory Available

It's handy to know how much RAM is allocated to each server, especially in a virtual machine environment. If you find that some are over-provisioned you can save valuable resources by right-sizing the servers. Thankfully, this is much simpler to retrieve.

Using the Win32_PhysicalMemory WMI class, we can sum all of the returned Capacity properties to get the total memory.

        (Get-CimInstance -ClassName Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum / 1GB
    

Get All of the Network Connections

Finally, we want to retrieve all of the network connections together. This is useful to know if a certain server has multiple interfaces to be worried about. Using a slightly different mechanism this time, we are using the Get-NetAdapter cmdlet, but since this one doesn't have a ComputerName parameter, we will use PS Remoting to invoke this locally on the target server and return the results to our script.

        $NetworkConnections = Invoke-Command -ComputerName $_.DnsHostName -ScriptBlock {
    Get-NetAdapter -Physical | Select-Object Name, Status, LinkSpeed
}

Our output will look similar to that below and we can then save this into our script.

Keep in mind that for Invoke-Command to work, PS Remoting will need to be set up on the target servers.

Putting it All Together

Now that we have all the pieces, let's put this all together. The final script is below and combines all the code to create a custom output object with just what we want to report on.

        Import-Module ActiveDirectory

$OU = 'OU=Servers,DC=domain,DC=local'

$Params = @{
    "SearchBase" = $OU
    "Filter" = '*'
}

$Servers = Get-ADComputer @Params

$Servers | Foreach-Object {
  $Disks = Get-CimInstance -ComputerName $_.DnsHostName -ClassName Win32_LogicalDisk

  $DisksResult = $Disks | Foreach-Object {
    [PSCustomObject]@{
      "Drive" = $_.DeviceID
      "FreeSpace" = [Math]::Round(($_.FreeSpace / 1GB),2)
    }
  }
  
  $NetworkConnections = Invoke-Command -ComputerName $_.DnsHostName -ScriptBlock {
    Get-NetAdapter -Physical | Select-Object Name, Status, LinkSpeed
  }

    [PSCustomObject]@{
        "ServerHostName" = $_.Name
        "Description" = $_.Description
        "FreeDiskSpace" = $DisksResult
        "TotalMemory" = ((Get-CimInstance -ComputerName $_.DnsHostName -ClassName Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum / 1GB)
        "NetworkConnections" = $NetworkConnections
        "PasswordLastSet" = $_.pwdLastSet
        "LastLogon" = $_.lastLogon
        "DNSHostName" = $_.DNSHostName
        "CreationDate" = $_.WhenCreated
    }
}

Conclusion

What we have demonstrated here is just the tip of the iceberg in terms of what can be built for an inventory report. There are many more useful properties that you can add on to this report. Taking this further, you could build this into an HTML page, schedule a task to run this weekly, or even wrap this in other tools such as Ansible.

PowerShell makes it trivially easy to get all the information you need to put together in one place. Once you analyze your environment and determine what you need to know, build the report in PowerShell to help future-proof your ability to audit your environment.