PowerShell tips for building objects with custom properties and special formatting

PowerShell tips for building objects with custom properties and special formatting

  • Comments 1
  • Likes

 

I’ve been building a number of scripts that query information using PowerShell.

To create those, I used some PowerShell tricks to make the output look just like I wanted. This post is a compilation of these tricks.

The first few tricks are simple, but they grow increasingly complex towards the end.

The examples here are geared towards SMB Shares and Volumes, but you can apply these concepts in many, many other situations.

 

1) Using –AutoSize

 

I do not like the default way that PowerShell spaces out fields in a query. I prefer them packed on the left, which is what the AutoSize option of the Format-Table cmdlet (FT for short) does.

AutoSize, as the name implies, also adjusts to the size of the properties and avoids showing “…” at the end of the output.

 

PS C:\> Get-SmbShare

Name                          ScopeName                     Path                          Description
----                          ---------                     ----                          -----------
ADMIN$                        *                             C:\windows                    Remote Admin
BackupFiles                   *                             E:\Backup                     Project backup files
C$                            *                             C:\                           Default share
D$                            *                             D:\                           Default share
F$                            *                             F:\                           Default share
Images                        *                             D:\Images                     Pictures to be used in pro...
IPC$                          *                                                           Remote IPC
print$                        *                             C:\windows\system32\spool\... Printer Drivers
Projects                      *                             C:\Projects                   Recent project files

 

PS C:\> Get-SmbShare | FT -AutoSize

Name        ScopeName Path                              Description
----        --------- ----                              -----------
ADMIN$      *         C:\windows                        Remote Admin
BackupFiles *         E:\Backup                         Project backup files
C$          *         C:\                               Default share
D$          *         D:\                               Default share
F$          *         F:\                               Default share
Images      *         D:\Images                         Pictures to be used in projects
IPC$        *                                           Remote IPC
print$      *         C:\windows\system32\spool\drivers Printer Drivers
Projects    *         C:\Projects                       Recent project files

 

2) Selecting columns to show

 

Every object has a default view with a specific set of columns. If you don’t like those, you can select your own. To find out what fields you can use, you can use a “Select *” or a “Get-Member” to find out.

 

PS C:\> Get-SmbShare | Select * -First 1

PresetPathAcl         :
ShareState            : Online
AvailabilityType      : NonClustered
ShareType             : FileSystemDirectory
FolderEnumerationMode : Unrestricted
CachingMode           : Manual
SmbInstance           : Default
CATimeout             : 0
ConcurrentUserLimit   : 0
ContinuouslyAvailable : False
CurrentUsers          : 0
Description           : Remote Admin
EncryptData           : False
Name                  : ADMIN$
Path                  : C:\windows
Scoped                : False
ScopeName             : *
SecurityDescriptor    : O:SYG:SYD:(A;;GA;;;BA)(A;;GA;;;BO)(A;;GA;;;IU)
ShadowCopy            : False
Special               : True
Temporary             : False
Volume                : \\?\Volume{4304337d-6763-11e3-8255-806e6f6e6963}\
PSComputerName        :
CimClass              : ROOT/Microsoft/Windows/SMB:MSFT_SmbShare
CimInstanceProperties : {AvailabilityType, CachingMode, CATimeout, ConcurrentUserLimit...}
CimSystemProperties   : Microsoft.Management.Infrastructure.CimSystemProperties

PS C:\> Get-SmbShare | Get-Member

   TypeName: Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/SMB/MSFT_SmbShare

Name                      MemberType     Definition
----                      ----------     ----------
Clone                     Method         System.Object ICloneable.Clone()
Dispose                   Method         void Dispose(), void IDisposable.Dispose()
Equals                    Method         bool Equals(System.Object obj)
GetCimSessionComputerName Method         string GetCimSessionComputerName()
GetCimSessionInstanceId   Method         guid GetCimSessionInstanceId()
GetHashCode               Method         int GetHashCode()
GetObjectData             Method         void GetObjectData(System.Runtime.Serialization.SerializationInfo info, Sys...
GetType                   Method         type GetType()
ToString                  Method         string ToString()
CATimeout                 Property       uint32 CATimeout {get;set;}
ConcurrentUserLimit       Property       uint32 ConcurrentUserLimit {get;set;}
ContinuouslyAvailable     Property       bool ContinuouslyAvailable {get;set;}
CurrentUsers              Property       uint32 CurrentUsers {get;set;}
Description               Property       string Description {get;set;}
EncryptData               Property       bool EncryptData {get;set;}
Name                      Property       string Name {get;}
Path                      Property       string Path {get;}
PSComputerName            Property       string PSComputerName {get;}
Scoped                    Property       bool Scoped {get;}
ScopeName                 Property       string ScopeName {get;}
SecurityDescriptor        Property       string SecurityDescriptor {get;set;}
ShadowCopy                Property       bool ShadowCopy {get;}
Special                   Property       bool Special {get;}
Temporary                 Property       bool Temporary {get;}
Volume                    Property       string Volume {get;}
AvailabilityType          ScriptProperty System.Object AvailabilityType {get=[Microsoft.PowerShell.Cmdletization.Gen...
CachingMode               ScriptProperty System.Object CachingMode {get=[Microsoft.PowerShell.Cmdletization.Generate...
FolderEnumerationMode     ScriptProperty System.Object FolderEnumerationMode {get=[Microsoft.PowerShell.Cmdletizatio...
PresetPathAcl             ScriptProperty System.Object PresetPathAcl {get=$acl = Get-Acl ($this.PSBase.CimInstancePr...
ShareState                ScriptProperty System.Object ShareState {get=[Microsoft.PowerShell.Cmdletization.Generated...
ShareType                 ScriptProperty System.Object ShareType {get=[Microsoft.PowerShell.Cmdletization.GeneratedT...
SmbInstance               ScriptProperty System.Object SmbInstance {get=[Microsoft.PowerShell.Cmdletization.Generate...

 

PS C:\> Get-SmbShare | FT Name, Path, Special, AvailabilityType -AutoSize

Name        Path                              Special AvailabilityType
----        ----                              ------- ----------------
ADMIN$      C:\windows                           True     NonClustered
BackupFiles E:\Backup                           False     NonClustered
C$          C:\                                  True     NonClustered
D$          D:\                                  True     NonClustered
F$          F:\                                  True     NonClustered
Images      D:\Images                           False     NonClustered
IPC$                                             True     NonClustered
print$      C:\windows\system32\spool\drivers   False     NonClustered
Projects    C:\Projects                         False     NonClustered

 

3) Selecting rows to show

 

You can restrict the items to show based on any of its properties. You need to learn to use the many types of operators like equal (-eq), not equal (-ne), greater than (-gt), like (-like), not like (-not like) and many others.

The cmdlet used is Where-Object, but it can be abbreviated as Where or simply a question mark. There is a simple form for querying just one property and also a more complex form for using expressions.

For instance, to show all shares that are not special, you can one of these:
Get-SmbShare | Where-Object {$_.Special –ne $true}
Get-SmbShare | ? Special –ne $true

If you use the form with the expression in {}, you must specify $_.property, where $_ means “the current item we’re processing”. The simpler form without {} can be used with only one property.

Here’s a more complex example to show all shares that are not special and with a description starting with the letter P:
Get-SmbShare | ? { $_.Special -ne $true -and $_.Description -like "P*" }

 

PS C:\> Get-SmbShare | Where-Object {$_.Special –ne $true} | FT -AutoSize

Name        ScopeName Path                              Description
----        --------- ----                              -----------
BackupFiles *         E:\Backup                         Project backup files
Images      *         D:\Images                         Pictures to be used in projects
print$      *         C:\windows\system32\spool\drivers Printer Drivers
Projects    *         C:\Projects                       Recent project files

PS C:\> Get-SmbShare | ? Special –ne $true| FT -AutoSize

Name        ScopeName Path                              Description
----        --------- ----                              -----------
BackupFiles *         E:\Backup                         Project backup files
Images      *         D:\Images                         Pictures to be used in projects
print$      *         C:\windows\system32\spool\drivers Printer Drivers
Projects    *         C:\Projects                       Recent project files

PS C:\> Get-SmbShare | ? Special | FT -AutoSize

Name   ScopeName Path       Description
----   --------- ----       -----------
ADMIN$ *         C:\windows Remote Admin
C$     *         C:\        Default share
D$     *         D:\        Default share
F$     *         F:\        Default share
IPC$   *                    Remote IPC

PS C:\> Get-SmbShare | ? { $_.Special -ne $true -and $_.Description -like "P*" } | FT -AutoSize

Name        ScopeName Path                              Description
----        --------- ----                              -----------
BackupFiles *         E:\Backup                         Project backup files
Images      *         D:\Images                         Pictures to be used in projects
print$      *         C:\windows\system32\spool\drivers Printer Drivers

 

4) Creating custom columns

 

In addition to the ability to select which columns to show, you can also create custom tables with any expression. This is useful for creating new calculated columns based on the existing ones.

You can also use this same process to rename existing properties (if you don’t like their default names) or customize the alignment/size of the column.

As with the full form of the “Where” filters, the expressions here must be enclosed in {} and references to existing properties must be preceded with $_.

The syntax uses a hash table, which is little unusual. You should include at least a “Label” (or “Name”) and an “Expression” item in the hash table. You can also specify “Alignment” (or “Align”) and “Width”.

Here’s an example to rename the “Path” property to “Folder”:
Get-SmbShare | FT Name, @{ Label=”Folder”; Expression={$_.Path} }, Description –AutoSize

Here’s another example showing only the drive letter of the path:
Get-SmbShare | FT Name, Description, @{ Name=”Drive”; Expression={$_.Path.Substring(0,1)}; Alignment=”Center” } –AutoSize

Lastly, a more complex example showing that shares ending with $ are hidden:
Get-SmbShare | FT Name, Path, @{ Align="Center"; Expression={If ($_.Name –like “*$”) {“Yes”} else {“No”} }; Label=”Hidden” } –AutoSize

It’s a lot of curly braces, I know. Just make sure you keep good track of them.

 

PS C:\> Get-SmbShare | FT Name, @{ Label=”Folder”; Expression={$_.Path} }, Description –AutoSize

Name        Folder                            Description
----        ------                            -----------
ADMIN$      C:\windows                        Remote Admin
BackupFiles E:\Backup                         Project backup files
C$          C:\                               Default share
D$          D:\                               Default share
F$          F:\                               Default share
Images      D:\Images                         Pictures to be used in projects
IPC$                                          Remote IPC
print$      C:\windows\system32\spool\drivers Printer Drivers
Projects    C:\Projects                       Recent project files

 
PS C:\> Get-SmbShare | FT Name, Description, @{ Name=”Drive”; Expression={$_.Path.Substring(0,1)}; Alignment=”Center” } –AutoSize

Name        Description                     Drive
----        -----------                     -----
ADMIN$      Remote Admin                      C
BackupFiles Project backup files              E
C$          Default share                     C
D$          Default share                     D
F$          Default share                     F
Images      Pictures to be used in projects   D
IPC$        Remote IPC
print$      Printer Drivers                   C
Projects    Recent project files              C

 

PS C:\> Get-SmbShare | FT Name, Path, @{ Align="Center"; Expression={If ($_.Name –like “*$”) {“Yes”} else {“No”} }; Label=”Hidden” } –AutoSize

Name        Path                              Hidden
----        ----                              ------
ADMIN$      C:\windows                         Yes
BackupFiles E:\Backup                           No
C$          C:\                                Yes
D$          D:\                                Yes
F$          F:\                                Yes
Images      D:\Images                           No
IPC$                                           Yes
print$      C:\windows\system32\spool\drivers  Yes
Projects    C:\Projects                         No

 

PS C:\> Get-Volume | Sort DriveLetter | FT DriveType, DriveLetter, FileSystem, @{Expression={ [int] ($_.Size/1MB) }; Label="Total (MB)"; Align="Right" }, @{Expression={ [int] (($_.Size-$_.SizeRemaining)/1MB) }; Label="Used (MB)"; Align="Right" }, @{Expression={ [int] ($_.SizeRemaining/1MB) }; Label="Free (MB)"; Align="Right" }, @{Expression={ [int]($_.SizeRemaining/$_.Size*100) }; Label="Free %"; Align="Right" } –AutoSize

DriveType DriveLetter FileSystem Total (MB) Used (MB) Free (MB) Free %
--------- ----------- ---------- ---------- --------- --------- ------
Fixed                 NTFS              350       287        63     18
Fixed               C NTFS           243845    214308     29537     12
Fixed               D NTFS           476937     56928    420009     88
Removable           E NTFS           125903     20925    104978     83
Fixed               F NTFS           476938    137181    339757     71

 

PS C:\> dir d:\*.* -Recurse | Select @{Expression={$_.CreationTime.Year};Label="YearCreated"}, @{Expression={$_.CreationTime.Month};Label="MonthCreated"} | Group YearCreated, MonthCreated | Select @{Expression={$_.Name.Split(",")[0].Trim()};Label="Year"}, @{Expression={$_.Name.Split(",")[1].Trim()};Label="Month"}, Count | Sort Count -Descending | FT Year, Month, Count -AutoSize

Year Month Count
---- ----- -----
2013 7     10172
2013 8      9114
2013 10     9097
2013 9      4075
2014 2       483
2013 4        47
2012 2        16
2013 11       15
2013 6         5
2013 12        4
2014 4         3
2013 5         2
2012 12        2
2012 5         1

 

 

5) Formatting numeric fields

 

One interesting trick when creating expressions is to format numbers. There is a specific –f operator that you can use to format numbers with comma separators, fixed number of decimal places, currency format and also percentages.

The format string can include references to multiple numbers, so you must specify the index for the number in {}. Here’s an example using 3 numbers.

PS C:\> "Note that {1} times {0} equals {2}" -f 123, 2, 246
Note that 2 times 123 equals 246

Beside the index, you can can specify a colon plus a letter indicating the type of formatting followed by the number of decimal points to use. Types of formatting include C for currency, N for Numeric (just comma separators), P for Percentage and E for Exponential. Here are a few examples:

PS C:\> "They spent {0:C2} of the total {1:C2} they had. They spent {2:P2}." -f 12.34, 45.67, (12.34/45.67)
They spent $12.34 of the total $45.67 they had. They spent 27.02 %.

PS C:\> "The number {0:N2} in exponential notation is {0:E6}" -f 123456.789
The number 123,456.79 in exponential notation is 1.234568E+005

Now if you combine this new formatting feature with the expressions the previous items, you can get quite powerful results:

PS C:\> Get-Volume | Sort DriveLetter | FT DriveType, DriveLetter, FileSystem, @{Expression={ "{0:N0}" -f ($_.Size/1MB) }; Label="Total (MB)"; Align="Right" }, @{Expression={ "{0:N0}" -f (($_.Size-$_.SizeRemaining)/1MB) }; Label="Used (MB)"; Align="Right" }, @{Expression={ "{0:N0}" -f ($_.SizeRemaining/1MB) }; Label="Free (MB)"; Align="Right" }, @{Expression={ "{0:P2}" -f ($_.SizeRemaining/$_.Size) }; Label="Free %"; Align="Right" } -AutoSize

DriveType DriveLetter FileSystem Total (MB) Used (MB) Free (MB)  Free %
--------- ----------- ---------- ---------- --------- ---------  ------
Fixed                 NTFS              350       287        63 18.09 %
Fixed               C NTFS          243,845   213,652    30,193 12.38 %
Fixed               D NTFS          476,937    56,928   420,009 88.06 %
Removable           E NTFS          125,903    20,925   104,978 83.38 %
Fixed               F NTFS          476,938   137,181   339,757 71.24 %

 

6) Linking two tables

 

One useful trick is to find the relationship between objects and create a single query that shows information coming from both of them.

For instance, you might want to show the free space in the volume when showing information about a share. Or you might want to show all volumes with their associated shares.

This usually involves looping through the items in one table using the For-Each cmdlet, which can be abbreviated as %. This also involves using $_ to refer to the properties of the table being looped through.

In other to show the correct association, you commonly need to use matching or related properties from both tables. If you come from the relational database world, think of a foreign key.

Because you’re dealing with two tables, you might need to assign the outer $_ to a specific variable, so you can compare fields from the outer table with the inner table.

Here’s an example showing all volumes and their associated file shares:
Get-Volume | Sort DriveLetter | % { $V=$_; $V | FT -AutoSize; Get-SmbShare | ? {$V.ObjectID –eq $_.Volume} | FT Name, Path -AutoSize }

Here’s another example showing all shares and the free space on the associated volume:
Get-SmbShare | ? Special -ne $true | % { $_ | FT Name, Path -AutoSize; Get-Volume –ObjectID $_.Volume | FT DriveLetterSize, SizeRemaining}

 

PS C:\> Get-Volume | Sort DriveLetter | % { $V=$_; $V | FT -AutoSize; Get-SmbShare | ? {$V.ObjectID –eq $_.Volume} | FT Name, Path -AutoSize }

DriveLetter FileSystemLabel FileSystem DriveType HealthStatus SizeRemaining   Size
----------- --------------- ---------- --------- ------------ -------------   ----
            System Reserved NTFS       Fixed     Healthy            63.3 MB 350 MB

DriveLetter FileSystemLabel FileSystem DriveType HealthStatus SizeRemaining      Size
----------- --------------- ---------- --------- ------------ -------------      ----
C           SSD1            NTFS       Fixed     Healthy           29.49 GB 238.13 GB

Name      Path
----      ----
ADMIN$    C:\windows
C$        C:\
print$    C:\windows\system32\spool\drivers
Projects  C:\Projects

DriveLetter FileSystemLabel FileSystem DriveType HealthStatus SizeRemaining      Size
----------- --------------- ---------- --------- ------------ -------------      ----
D           HDD             NTFS       Fixed     Healthy          410.17 GB 465.76 GB

Name   Path
----   ----
D$     D:\
Images D:\Images

DriveLetter FileSystemLabel FileSystem DriveType HealthStatus SizeRemaining      Size
----------- --------------- ---------- --------- ------------ -------------      ----
E           SDCard          NTFS       Removable Healthy          102.52 GB 122.95 GB

Name        Path
----        ----
BackupFiles E:\Backup

DriveLetter FileSystemLabel FileSystem DriveType HealthStatus SizeRemaining      Size
----------- --------------- ---------- --------- ------------ -------------      ----
F           USB3            NTFS       Fixed     Healthy          331.79 GB 465.76 GB

Name Path
---- ----
F$   F:\

 

PS C:\> Get-SmbShare | ? Special -ne $true | % { $_ | FT Name, Path -AutoSize; Get-Volume –ObjectID $_.Volume | FT DriveLetter, Size, SizeRemaining}

Name        Path
----        ----
BackupFiles E:\Backup

DriveLetter         Size SizeRemaining
-----------         ---- -------------
          E 132018860032  110077665280

Name   Path
----   ----
Images D:\Images

DriveLetter         Size SizeRemaining
-----------         ---- -------------
          D 500104687616  440411648000

Name   Path
----   ----
print$ C:\windows\system32\spool\drivers

DriveLetter         Size SizeRemaining
-----------         ---- -------------
          C 255690010624   31659585536

Name     Path
----     ----
Projects C:\Projects

DriveLetter         Size SizeRemaining
-----------         ---- -------------
          C 255690010624   31659585536

 

7) Creating a few fake fields

 

Another useful trick is to create a temporary object with a few fake properties that you can fill in with some scripting. This requires using variables to store the temporary results and a little more understanding of programming structures like loops.

This provides a better way to combine two related tables into a single view, like we did in the previous item. But this time we get a single, combined output.

For instance, Get-SmbShare will return a volume ID that you can link to a volume, but it would be nice if we could have a single query with some properties from the share and some from the volume.

Here’s what you would do: First, create a variable with the result of Get-SmbShare, including the properties you want to fill later. These won’t exist initially, so they start empty. Then we’ll loop through the items and fill those empty properties using a query for the volume ID. After that, the results are ready to be formatted using any of our previous tricks…

 

PS C:\> $Shares = Get-SmbShare | ? { $_.Special -ne $true} | Select Name, Path, Volume, Total, Used, Free
 

PS C:\> $Shares | FT -AutoSize

Name        Path                              Volume                                            Total Used Free
----        ----                              ------                                            ----- ---- ----
BackupFiles E:\Backup                         \\?\Volume{a68e6379-6763-11e3-8256-d89d67d108b9}\
Images      D:\Images                         \\?\Volume{4304337f-6763-11e3-8255-806e6f6e6963}\
print$      C:\windows\system32\spool\drivers \\?\Volume{4304337d-6763-11e3-8255-806e6f6e6963}\
Projects    C:\Projects                       \\?\Volume{4304337d-6763-11e3-8255-806e6f6e6963}\

PS C:\> $Shares | % { $S=$_; $V=Get-Volume | ? ObjectId -eq $S.Volume; $S.Total=$V.Size/1MB; $S.Free=$V.SizeRemaining/1MB; $S.Used=($V.Size-$V.SizeRemaining)/1MB }
 

PS C:\> $Shares | Sort Name | FT Name, Path, @{Expression={ "{0:N0}" -f $_.Total }; Label="Total (MB)"; Align="Right" }, @{Expression={ "{0:N0}" -f $_.Used }; Label="Used (MB)"; Align="Right" }, @{Expression={ "{0:N0}" -f $_.Free }; Label="Free (MB)"; Align="Right" }, @{Expression={ "{0:P2}" -f ($_.Free/$_.Total) }; Label="Free %"; Align="Right" } -AutoSize

Name        Path                              Total (MB) Used (MB) Free (MB)  Free %
----        ----                              ---------- --------- ---------  ------
BackupFiles E:\Backup                            125,903    20,925   104,978 83.38 %
Images      D:\Images                            476,937    56,928   420,009 88.06 %
print$      C:\windows\system32\spool\drivers    243,845   213,652    30,193 12.38 %
Projects    C:\Projects                          243,845   213,652    30,193 12.38 %

 

8) Creating entirely fake tables

 

You can also create entirely new objects using a trick similar to the previous one. You start by creating a new item simply piping an empty variable to a Select cmdlet that specifies the columns you want to create.

PS C:\> $Alerts = "" | Select Severity, Entity, Instance, Description

Then you can use any cmdlets to populate that item’s columns and sent them out.

PS C:\> Get-Volume | ? {($_.SizeRemaining/$_.Size) -lt 0.8} | % { $Alerts.Severity="Warning"; $Alerts.Entity="Volume"; $Alerts.Instance = $_.DriveLetter+" "+$_.ObjectID; $Alerts.Description="Volume has less than 80% free space"; $Alerts} | FT -AutoSize

Severity Entity Instance                                            Description
-------- ------ --------                                            -----------
Warning  Volume   \\?\Volume{4304337c-6763-11e3-8255-806e6f6e6963}\ Volume has less than 80% free space
Warning  Volume F \\?\Volume{c9337eca-6774-11e3-825a-b8763fd91487}\ Volume has less than 80% free space
Warning  Volume C \\?\Volume{4304337d-6763-11e3-8255-806e6f6e6963}\ Volume has less than 80% free space

This gets even more useful if you pack it as a PowerShell function, which you can name just like a regular cmdlet:

Function Get-StorageAlert
{

    $A = "" | Select Severity, Entity, Instance, Description

    Get-Volume | ? DriveLetter | ? {($_.SizeRemaining/$_.Size) -lt 0.8} | % {
        $A.Severity="Warning"
        $A.Entity="Volume"
        $A.Instance = $_.DriveLetter
        $A.Description="Volume has less than 80% free space"
        $A
    }

    Get-SmbShare -ContinuouslyAvailable $false | ? {$_.Special -ne $true} | % {
        $A.Severity="Warning"
        $A.Entity="Share"
        $A.Instance = $_.Name
        $A.Description="Share is not continuously available"
        $A
    }

}

Here’s a sample output:

PS C:\> Get-StorageAlert | FT -AutoSize

Severity Entity             Instance Description
-------- ------             -------- -----------
Warning  Volume                    F Volume has less than 80% free space
Warning  Volume                    C Volume has less than 80% free space
Warning  Share           BackupFiles Share is not continuously available
Warning  Share                Images Share is not continuously available
Warning  Share                print$ Share is not continuously available
Warning  Share              Projects Share is not continuously available

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Very useful
    Thanks