The Admin’s First Steps: Capacity Planning Part 1

Summary: Richard Siddaway talks about using Windows PowerShell to collect data for capacity planning.

Hey, Scripting Guy! Question Hey, Scripting Guy! I’ve just starting learning Windows PowerShell and I understand how to use it as a scripting language and shell. I’ve been told to start performing capacity planning for my servers and was wondering how I can use Windows PowerShell to help with this task.


Hey, Scripting Guy! Answer Hello PH,

Honorary Scripting Guy, Richard Siddaway, here today—filling in for my good friend, The Scripting Guy. As part of The Admin’s First Steps series, I’m going to devote the next three posts to capacity planning. Hopefully, this will answer your question.

To view the previous posts in this series, see The Admin’s First Steps Series.

Capacity planning is all about answering questions such as, “When will file servers run out of disk space?” and “How many more users will this application support before the server becomes too slow?”

You can’t answer these questions exactly. It would be great to be able to know that file server File01 will run out of disk space on 14 June 2014 at 10:37 AM. You could then plan to add extra capacity before that time so that your users don’t experience issues. Unfortunately in the real world, things don’t work like that, and you can’t make such exact predictions.

However, what you can do is measure the trend of usage. If the disk space that is used by your file server is growing at 2% per month, you can predict when you need to add disk space—assuming that the rate of usage remains constant. That last part is important. You have to assume that the trends will continue.

Capacity planning is a three stage process:

  • Data gathering
  • Data storage
  • Reporting on that data

In this post, we’ll look at how to collect data. In the following blog posts, I will discuss how you can store the data and how to create the reports.

Before you can start collecting data, you need to know what you want to show. The two main groups you need to worry about are:

  • Space issues: When will my disks be full? When will I have used all of the memory if each additional user requires another 2 MB of RAM for the application?
  • Performance issues: Can my processors cope with adding 50 more users to the server? Will the network cards cope with the extra traffic from those users?

Collecting space-type data is relatively straightforward because it tends to be a one-way process with usage growing (unless you do some cleaning up). So recording data every couple of weeks or every month may be sufficient.

Collecting disk space data can be accomplished by using WMI. The following function provides one way to perform this task:

function get-diskcapacity {


param (

 [string[]]$computername = $env:COMPUTERNAME



foreach ($computer in $computername) {

Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" -ComputerName $computer |

select PSComputerName, Caption,

@{N='Capacity_GB'; E={[math]::Round(($_.Size / 1GB), 2)}},

@{N='FreeSpace_GB'; E={[math]::Round(($_.FreeSpace / 1GB), 2)}},

@{N='PercentUsed'; E={[math]::Round(((($_.Size - $_.FreeSpace) / $_.Size) * 100), 2) }},

@{N='PercentFree'; E={[math]::Round((($_.FreeSpace / $_.Size) * 100), 2) }}

} # end foreach

} # end PROCESS


The function takes an array of computer names as input. It loops through those computers and uses Get-CimInstance to retrieve the Win32_LogicalDisk class information from the machine. A filter limits the data to local hard disks. Other potential filters can be found in Windows PowerShell and WMI or online at Win32_LogicalDisk class.

WMI returns the disk size and free space as bytes. This isn’t the easiest of values to relate to, so the function converts those values to GB. Notice the use of the Windows PowerShell size constant for 1 GB rather than typing in the full value. The percentages of the disk that are used and free are also calculated. All calculations are rounded to two decimal places.

Using Get-CIMinstance in this manner assumes that you have Windows PowerShell 3.0 on all of the remote machines.  If you don’t (especially if you have a mixture of Windows PowerShell 3.0  and Windows PowerShell 2.0), you should use Get-WmiObject instead.

function get-diskcapacity {


param (

 [string[]]$computername = $env:COMPUTERNAME



foreach ($computer in $computername) {

Get-WmiObject -Class Win32_LogicalDisk -Filter "DriveType = 3" -ComputerName $computer |

select PSComputerName, Caption,

@{N='Capacity_GB'; E={[math]::Round(($_.Size / 1GB), 2)}},

@{N='FreeSpace_GB'; E={[math]::Round(($_.FreeSpace / 1GB), 2)}},

@{N='PercentUsed'; E={[math]::Round(((($_.Size - $_.FreeSpace) / $_.Size) * 100), 2) }},

@{N='PercentFree'; E={[math]::Round((($_.FreeSpace / $_.Size) * 100), 2) }}

} # end foreach

} # end PROCESS


The reason Get-CimInstance won’t work against the machines running Windows PowerShell 2.0 is the WSMAN version. The CIM cmdlets use WSMAN to connect to remote machines, and they require WSMAN 3.0, or above, which means at least Windows PowerShell 3.0 must be installed.

Windows Server 2012 and Windows 8 have a NetAdapter module. (It’s not available in earlier versions of Windows because it uses the ROOT/StandardCimv2:MSFT_NetAdapterStatisticsSettingData WMI class, which isn’t ported to the older versions.)

Note   Details for the cmdlet can be found in the Help file. Or if you don’t have access to a computer running Windows Server 2012 or Windows 8, it’s available online at Get-NetAdapterStatistics. The WMI class documentation can be found on MSDN at MSFT_NetAdapterStatisticsSettingData class. I’d recommend examining the output of this class because there is a lot of useful information. The WMI class documentation at MI Classes has been expanded to include a lot of the WMI classes that were introduced in Windows 8. Book mark the page if you use WMI!

The cmdlet is used like this:

Get-NetAdapterStatistics -Name WiFi |

select ReceivedBytes, SentBytes,

ReceivedDiscardedPackets, ReceivedPacketErrors,

OutboundDiscardedPackets, OutboundPacketErrors

The name of the adapter is used to limit the data returned. If you have multiple adapters in the system, you may want to return data for all of them. The selected properties show the number of bytes sent and received, together with error data for inbound and outbound connections.

The output looks like this:

ReceivedBytes            : 1290212919

SentBytes                : 163510700

ReceivedDiscardedPackets : 0

ReceivedPacketErrors     : 0

OutboundDiscardedPackets : 0

OutboundPacketErrors     : 0

The statistics are cumulative from when the machine started. However, the data collected is only for a point in time. You need to be able to track usage across a time period. I normally use the busiest parts of the working day (mid-morning and mid-afternoon) to collect information when the systems are under the greatest load. You can achieve that by taking two readings separated by a known time interval and calculating the difference between the various values. Because you want this to run for a significant period, this job is an ideal candidate for Windows PowerShell.

$sb = {

param ([string]$computername)

Import-Module NetAdapter


$sess = New-CimSession -ComputerName $computername

$start = Get-NetAdapterStatistics -Name WiFi -CimSession $sess

Start-Sleep -Seconds 60  # 1800 = 30 minutes


$end = Get-NetAdapterStatistics -Name WiFi -CimSession $sess

Remove-CimSession $sess


$props = [ordered]@{

 ComputerName = $computername

 ReceivedBytes = $end.ReceivedBytes - $start.ReceivedBytes

 SentBytes = $end.SentBytes - $start.SentBytes

 ReceivedDiscardedPackets = $end.ReceivedDiscardedPackets - $start.ReceivedDiscardedPackets

 ReceivedPacketErrors = $end.ReceivedPacketErrors - $start.ReceivedPacketErrors

 OutboundDiscardedPackets = $end.OutboundDiscardedPackets -$start.OutboundDiscardedPackets

 OutboundPacketErrors = $end.OutboundPacketErrors -$start.OutboundPacketErrors



New-Object -TypeName PSObject -Property $props




Start-Job -ScriptBlock $sb  -ArgumentList $env:COMPUTERNAME  -Name "netstats-$($env:COMPUTERNAME)"

A script block is used to define the processing. It starts by importing the Netadapter module. Windows PowerShell jobs run a separate context, and although Windows PowerShell 3.0 will auto-load modules, I always prefer to categorically import the module. The Get-NetAdapterStatistics cmdlet is used to populate the $Start variable, and it uses a CIM session to the remote machine. Start-Sleep pauses the processing. In this case, I’ve used 60 seconds; but in reality, you’d probably want to use 30 minutes or more.

A second call to Get-NetAdapterStatistics over the CIM session, is used to get the end values. An ordered hash table is created with the values being calculated by subtracting the start values from the end values. The computer name is added as another property. Ordered hash tables keep the properties in the order you define rather than “randomizing” them as with a normal hash table. The hash table is used as the properties for a new object, which forms the output.

Start-Job is used to execute the script block. The job is given a name based on the computer name, with the computer name being passed to the script block as an argument.

Ways you could extend this include:

  • Add extra items to the collection process
  • Create function to build jobs for retrieving network statistics from remote machines

PH, that is how you go about gathering data for your capacity planning. Obviously, there are other parameters that you can measure, but they are dependent on your environment. I would recommend starting with a small number of measurements and expand that over time. Next time, we’ll look at storing the data you are collecting.

Bye for now.


Thanks, Richard. I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Comments (5)

  1. Jer says:

    Very useful, thanks. New similar data gathering question. Is there a way to quickly gather the 'total number of files' on each drive on my servers?

  2. Gary says:

    Love this series, shows how powershell is used in the real world.

  3. The right question and the right answer. Thanks Ed.

  4. Chen V says:

    Much Appreciated!!!

  5. I glossed over a fact in this post when I said Get-CimInstance requires WSMAN 3 on the remote machine.  You can work with a DCOM CIM session to access a machine running WSMAN 2 (PowerShell 2)

    $opt = New-CimSessionOption -Protocol DCOM

    $sess = New-CimSession -ComputerName RSLAPTOP01 -SessionOption $opt

    Get-CimInstance -ClassName Win32_LogicalDisk -CimSession $sess

    The reason for me saying use Get-WmiObject if you have PowerShell 2 machines, or a mixture, was one of simplicity. If you can connect over DCOM to all machines it means that you don't have to add any logic to determine what the remote machine is running.

Skip to main content