Formatting output

Guest Post — Get-myFreeSpace Revisited

Today's post comes by way of a co-worker, Robert Carlson, who took my previous post on getting the free disk space of remote computers and offered a very useful suggestion -- instead of outputting strings, which is only useful for a display or report, he suggests creating a PSCustomObject and outputting that. Slick! I like it.

 

So, why a PSCustomObject? Because now he can use it to drive automation, rather than simply reporting. A very handy change, and a good reminder for all of us that we should put off formatting until the last possible moment, because once you pipe something to Format-*, you're done. All your precious objects and their properties are gone, and you're left with a simple string.

 

The other thing Robert has done is change this from a script to a function. This makes it easier to call from other scripts and allows it to be added to your "toolbox" module. (More on Toolbox Modules soon. )  A worthy change. So, without further ado, here's Robert's revised Get-myFreeSpace function.

function Get-myFreeSpace {
<#
.Synopsis
Gets the disk utilization of one or more computers
 
.Description
Get-myFreeSpace queries an array of remote computers and returns a nicely formatted display of 
their current disk utilization and free space. The output can be redirected to a file or other 
output option using standard redirection, or can be piped to further commands.

.Parameter ComputerName
An array of computer names from which you want the disk utilization

.Example
(Get-VM -Name “*server*” | Where-Object {$_.State -eq ‘Running’).Name } | Get-myFreeSpace
Gets the free disk space of the running virtual machines whose name includes 'server'

.Inputs
[string[]]

.Notes
 Original Author: Charlie Russel
Secondary Author: Robert Carlson
Copyright: 2017 by Charlie Russel
         : Permission to use is granted but attribution is appreciated
  Initial: 26 Nov, 2014 (cpr)
  ModHist: 29 Sep, 2016 — Changed default to array of localhost (cpr)
         : 18 Apr, 2017 — Changed to use Write-Output,accept Pipeline,added man page, (cpr)
         : 20 Apr, 2017 — Changed output to pscustomobject rather than string, etc.(RC)
#>

[CmdletBinding()]
Param(
[Parameter(Mandatory=$False,Position=0,`
           ValueFromPipeline=$True,`
           ValueFromPipelineByPropertyName=$True,`
           ValueFromRemainingArguments=$True)]
           [alias(“Name”,”Computer”)]
           [string[]]
           $ComputerName = @(“localhost”)
           )

Begin {
   if ($Input) {
      $ComputerName = @($Input) 
   }
}

Process {
   ForEach ( $Computer in $ComputerName ) {
      $volumes = Get-WmiObject -ComputerName $Computer -Class Win32_Volume -ErrorAction SilentlyContinue 
      foreach ($volume in $volumes) {
         $volumeData = [pscustomobject]@{
            ComputerName=$Computer 
            Drive=$volume.DriveLetter
            VolumeLabel=$volume.Label
            VolumeSize=”{0:N0}” -f ($volume.Capacity / 1GB)
            FreeSpace=”{0:N0}” -f ($volume.FreeSpace/1GB)
            }
         if ($volume.Capacity) {
            $percentage = “{0:P0}” -f ($volume.FreeSpace / $volume.Capacity)
            $volumeData | Add-Member -NotePropertyName “PercentageFree” -NotePropertyValue $percentage
         } else {
            $volumeData | Add-Member -NotePropertyName “PercentageFree” -NotePropertyValue “n/a”
         }
         Write-Output $volumeData
      }
   }
 }
}

I really appreciate Robert's contribution, and I thank him profoundly for his suggestion. I learned something, and I hope you have too.  I hope you found this useful, and I'd love to hear comments, suggestions for improvements, or bug reports as appropriate. As always, if you use this script as the basis for your own work, please respect my copyright and provide appropriate attribution.

Getting the Free Disk Space of Remote Computers Revisited

Several years ago, I wrote a fairly simplistic script to get the free disk space of remote computers. It wasn't all that sophisticated, but it got the job that I needed done, so I shared it here on my blog, since I thought others might find it useful. Which, based on the number of hits here, and the comments, they did. However, based on some of those comments, it had a problem for some users.

 

The problem was that I used Write-Host in it. That was fine for me, because I only used it to write to my screen. But it's a bad practice to be using Write-Host unless you really need to manipulate screen colours. The reason it's a bad practice is that it prevents any sort of redirection! This meant that those users who wanted to capture the result of the script in a file were horked, because Write-Host will ALWAYS write to ( ... wait for it...  )

 

The Host. You can't redirect it. The fix, of course, is easy -- use Write-Object instead, which is what I should have done in the first place.

 

While I was in the process of making that change, I thought it would be nice to add in a basic Get-Help page for it, which was trivial. But then it occurred to me that I really should let it handle pipeline input, allowing me to use other PowerShell commands to select the group of machines I wanted the free disk space on, and then pipe that result directly to Get-myFreeSpace.

 

Seemed like a good idea, but it turned out I had to almost completely rewrite the script to use the Begin{}/Process{}/End{} syntax. Accepting pipeline input is not as simple as just saying you do in the Parameter statement, you need to actually process that input. The result is the new, improved version of Get-myFreeSpace.ps1 shown below. (If you care about how I got to this script in the first place, do check out the original post, here. There's some useful information there about the whole process. )

 

<#
.Synopsis
Gets the disk utilization of one or more computers

.Description
Get-myFreeSpace queries an array of remote computers and returns a nicely formatted display of 
their current disk utilization and free space. The output can be redirected to a file or other 
output option using standard redirection. 

.Example
Get-myFreeSpace 
Gets the disk utilization and free space of all drives on the local host. 

.Example
Get-myFreeSpace -ComputerName Server1,Server2
Gets the disk utilization and free space of all drives on the Server1 and Server2

.Example
(Get-VM -Name "*server*" | Where State -eq 'Running' ).Name | Get-myFreeSpace
PS C:\>(Get-VM -Name "*server*" | Where-Object {$_.State -eq 'Running').Name | Get-myFreeSpace

Gets a list of running VMs with Server in their name, and passes it to Get-myFreeSpace to process for 
their current disk utilization. The first version of this example uses PowerShell v5 syntax, while 
the second version uses the older syntax that works on earlier versions. 
.Parameter ComputerName
An array of computer names from which you want the disk utilization

.Inputs
[string[]]

.Notes
    Author: Charlie Russel
 Copyright: 2017 by Charlie Russel
          : Permission to use is granted but attribution is appreciated
   Initial: 26 Nov, 2014 (cpr)
   ModHist: 29 Sep, 2016 -- Changed default to array of localhost (cpr)
          : 18 Apr, 2017 -- Changed to use Write-Output,accept Pipeline,added man page,  (cpr)
          :
#>
[CmdletBinding()]
Param(
     [Parameter(Mandatory=$False,Position=0,`
                ValueFromPipeline=$True,`
                ValueFromPipelineByPropertyName=$True,`
                ValueFromRemainingArguments=$True)]
     [alias("Name","Computer")]
     [string[]]
     $ComputerName = @("localhost")
     )

Begin {
   if ($Input) {
      $ComputerName = @($Input)
   } 
   Write-Output ""
   # Save ErrorActionPreference so we can reset it when we're done
   $eap = $ErrorActionPreference
}

Process {
   $ErrorActionPreference = 'SilentlyContinue'
   ForEach ( $Computer in $ComputerName ) {
      Write-Output "Disk Utilization for Computer $Computer is: " 
      Get-WmiObject  -ComputerName $Computer -Class Win32_Volume `
         | Format-Table  -auto `
            @{Label="Drive";`
               Expression={$_.DriveLetter};`
               Align="Right"},`
            @{Label="Free(GB)";`
               Expression={"{0:N0}" -f ($_.FreeSpace/1GB)};`
               Align="Right"},`
            @{Label="% Free";`
               Expression={"{0:P0}" -f ($_.FreeSpace / $_.Capacity)};`
               Align="Right"},`
            @{Label="Size(GB)";`
               Expression={"{0:N0}" -f ($_.Capacity / 1GB)};`
               Align="Right"},`
            @{Label="Volume Label";`
               Expression={$_.Label};`
               Width=25}
      } #EndForEach
} #EndProcessBlock

End {
   # Reset ErrorActionPreference to original value
   $ErrorActionPreference = $eap
}

And there you have it. A new and improved version of one of the most popular scripts I've ever posted here. You can use it to get the disk utilization on your current machine, or any list of remote computers to which you have the rights to run WMI against.

 

I hope you find this script useful, and I'd love to hear comments, suggestions for improvements, or bug reports as appropriate. As always, if you use this script as the basis for your own work, please respect my copyright and provide appropriate attribution.

Getting the Free Disk Space of Remote Computers

(For an updated version of this post and script, see Getting the Free Disk Space of Remote Computers Revisited.)

This started out as a simple script to try to get the free space on one of my servers, but I quickly discovered that using WMI’s Win32_LogicalDisk could only give me part of the solution. The catch is that Win32_LogicalDisk doesn’t return the information about volumes that aren’t assigned drive letters. Which is a problem if what you really need to know is how close to out of space your backup disk is! Because Windows Server Backup takes total control of the backup target disk, and doesn’t mount it as a drive letter, you need to use a different WMI Class to get the information you need. After asking some friends (PowerShell MVPs ROCK!), I was pointed to Win32_Volume, which returns all sorts of information about disk volumes whether they are assigned drive letters or not.

The next issue was how to get the actual information I wanted, and then to format it so that it actually made some sense. For example:

(Get-WmiObject –ComputerName Server1 –Class Win32_Volume).FreeSpace
21654667264
103541030912
75879378944
142417367040
5500928
565375053824
PSH>

 

This doesn’t really cut it. OK, let’s try at least getting it into a table:

Get-WmiObject –ComputerName Server1 –Class Win32_Volume | ft –auto DriveLetter,Label,FreeSpace

DriveLetter Label                               FreeSpace 
----------- -----                               --------- 
C:                                            21655351296 
D:          DATA                             103541030912 
E:          EXCHANGE                          75879378944 
F:          FILES                            142417367040 
Y:          New Volume                            5500928 
            Server1 2014_10_15 10:57 DISK_03 565375053824

 

Well, that’s a bit more useful, but frankly, that number for the backup volume seems big, but is it 500 GB, or 50 GB? At first glance, I have no idea. And if it’s 50 GB, I’m in trouble, but if it’s 500 GB, we’re fine. So, we need to do a bit of manipulation to the output from Format-Table, and the tool for this is to create an Expression that allows you to calculate and format a result in a way that makes more sense. For this, we use an expression as the property to display. So, for example, to display that “565375053824” as Gigabytes, we use:

Get-WmiObject –ComputerName Server1 –Class Win32_Volume `
       | ft –auto DriveLetter,`
                  Label,`
                  @{Label=”Free(GB)”;Expression={'{0:N0}’ –F ($_.FreeSpace/1GB)}}
DriveLetter Label                            Free(GB) 
----------- -----                            -------- 
C:                                           20 
D:          DATA                             96 
E:          EXCHANGE                         71 
F:          FILES                            133 
Y:          New Volume                       0 
            Server1 2014_10_15 10:57 DISK_03 527

Now we’re getting somewhere. But what did we do? We use the @{} to tell Format-Table that we were going to use an expression to define a column of data. The Label=”Free(GB)” creates a new column header, and the Expression={“{0:N0}” –F  means we’re going to have a numeric value (including thousands separators) with no decimal values. The calculated value for the column is ($_.FreeSpace/1GB).

So we now have a useful listing of free space on the remote server. Of course, it might be even more useful to know the percentage free. No problem, for that we use the formatting expression “{0:P0}” to express the column as a percentage, and use the calculation of ($_.FreeSpace/$_.Capacity), letting PowerShell do the work of converting that to a percentage. So:

Get-WmiObject –ComputerName Server1 –Class Win32_Volume `
       | ft –auto DriveLetter,`
                  Label,`
                  @{Label=”Free(GB)”;Expression={“{0:N0}” –F ($_.FreeSpace/1GB)}},`
                  @{Label=”%Free”;Expression={“{0:P0}” –F ($_.FreeSpace/$_.Capacity)}}
DriveLetter Label                         Free(GB)  %Free
----------- -----                         --------  ----- 
C:                                            20      17 %
D:          DATA                              96      48 % 
E:          EXCHANGE                          71      71 % 
F:          FILES                             133     18 % 
Y:          New Volume                        0       58 % 
            Server1 12014_10_15 10:57 DISK_03 527     51 %
<br>

 

Now we almost have it. Next, it would probably be useful to get the total capacity of the disk while we’re at it, and since I have more than one server, we should probably plan on passing this whole thing an array of computer names. So, the final script, at least for this first pass:

# ********************************************* 
# ScriptName: Get-myFreeSpace.ps1 
# 
# Description: Script to get the free disk space
#            : on a remote computer and display it usefully
# 
# ModHist: 26/11/2014 - Initial, Charlie
#        : 18/04/2017 - Change to Write-Output. 
# 
# 
# *********************************************
[CmdletBinding()]
Param ([Parameter(Mandatory=$False,Position=0)]
         [String[]]$ComputerName = "Server1")
Write-Output ""
ForEach ( $Name in $ComputerName ) {
   Write-Output "Disk Utilization for server $Name is: "
   Get-WmiObject  -ComputerName $Name -Class Win32_Volume `
      | Format-Table  -auto `
         @{Label="Drive";`
            Expression={$_.DriveLetter};`
            Align="Right"},`
         @{Label="Free(GB)";`
            Expression={"{0:N0}" -f ($_.FreeSpace/1GB)};`
            Align="Right"},`
         @{Label="% Free";`
            Expression={"{0:P0}" -f ($_.FreeSpace / $_.Capacity)};`
            Align="Right"},`
         @{Label="Size(GB)";`
            Expression={"{0:N0}" -f ($_.Capacity / 1GB)};`
            Align="Right"},`
         @{Label="Volume Label";`
            Expression={$_.Label};`
            Width=25} 
}

You’ll see I tweaked the formatting to right align the calculated expressions, and gave my volume label column some extra space to improve readability. The result is:

Get-myFreeSpace.ps1 –ComputerName “Server1”,”Server2” 
 
Disk Utilization for server Server1 is:

 Drive Free(GB) % Free Size(GB) Volume Label 
----- -------- ------ -------- ------------ 
   C:       20   17 %      120 
   D:       96   48 %      200 DATA 
   E:       71   71 %      100 EXCHANGE 
   F:      133   18 %      750 FILES 
   Y:        0   58 %        0 New Volume 
           527   51 %    1,024 Server1 2014_10_15 10:57 DISK_03 
 
Disk Utilization for server Server2 is:

Drive Free(GB) % Free Size(GB) Volume Label 
----- -------- ------ -------- ------------ 
             0   25 %        0 
   D:    1,697   53 %    3,214 Data 
   C:      484   95 %      512

 

ETA:  Changed to use Write-Output instead of Write-Host. No need for the features of Write-Host, and it was causing issues for those who wanted to redirect the output to a file. This is more flexible.

ETA:  Changed to clean up the problem formatting from the switch in syntax highlighters. Sigh.

 

 

Getting Drive Information Using WMI and PowerShell

In my ongoing quest to remove dependency on legacy DOS commands, I recently created this script to get a list of all the local and mapped drives on a machine and how much free space each has on it.  This script could be easily extended to display additional information (such as the underlying filesystem type) by simply adding to the output tables. All the information is already captured in the Get-WMIObject call at the beginning, but the fun is in the formatting (and math)  of the tables to provide useful information at a glance.

 # Filename: GetDrives.ps1
 #
 # A script to get a list and information on the "local" drives without using "Net"
 #
 # ModHist: 08/05/2013 - Initial
 #                   :
 #
 # ***********************************************************************
 $LogicalDisks = Get-WMIObject Win32_LogicalDisk
 $LocalHDisks = $LogicalDisks | Where-Object { $_.DriveType -eq 3 }
 $RemoteDrives = Get-WMIObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq 4 }
 $OpticalDrives = Get-WMIObject Win32_CDRomDrive
$LocalHDisks | ft -auto @{Label="Drive";`
                           Expression={$_.DeviceID};
                           width=5
                           align="Right"},`
                        @{Label="Volume Label";`
                           Expression={$_.VolumeName};                                                         
                           Width=25},` 
                        @{Label="%Free";`
                           Expression={[int]($_.FreeSpace/$_.Size * 100)};`
                           Width=8},`
                        @{Label="GBFree";`
                           Expression={$([math]::round(($_.FreeSpace/1gb),0))};`
                           Width=8},`
                        @{Label="Size(GB)";`
                           Expression={$([math]::round(($_.Size/1gb),0))};`
                           Width=8}
 $OpticalDrives | Sort Drive | ft -auto `
                        @{Label="Drive";`
                           Expression={$_.Drive};`
                           Width=5
                           Align="Right"},
                        @{Label="Capabilities";`
                           Expression={ if ($_.Capabilities -eq 4) { `
                             "Read/Write" `
                           } else { `
                             "Read-Only"}}},`
                        @{Label="Disk Volume Label";`
                           Expression={$_.VolumeName}}
 $RemoteDrives | ft -auto @{Label="Drive";`
                           Expression={$_.DeviceID};`
                           width=5
                           align="Right"},`
                        @{Label="Remote Share";`
                           Expression={$_.ProviderName};`
                           Width=25},
                        @{Label="%Free";`
                           Expression={[int]($_.FreeSpace/$_.Size * 100)};`
                           Width=8},`
                        @{Label="GBFree";`
                           Expression={$([math]::round(($_.FreeSpace/1gb),0))};`
                           Width=8},`
                        @{Label="Size(GB)";`
                           Expression={$([math]::round(($_.Size/1gb),0))};`
                           Width=8}

 

If you find this script interesting or useful, great. And if you have improvements to it, I'd be delighted to hear about them.

Charlie.