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.
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
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
}
Get-PhysicalDisk | % {
$PD = $_.FriendlyName
$_ | Get-StorageReliabilityCounter | % {
If ($_.ReadLatencyMax -gt 500) {
$A.Severity="Warning"
$A.Entity="Physical Disk"
$A.Instance = $PD
$A.Description="Max read latency higher than 500 ms"
$A
}
If ($_.WriteLatencyMax -gt 500) {
$A.Severity="Warning"
$A.Entity="Physical Disk"
$A.Instance = $PD
$A.Description="Max write latency higher than 500 ms"
$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
Warning Physical Disk PhysicalDisk0 Max read latency higher than 500 ms
Warning Physical Disk PhysicalDisk0 Max write latency higher than 500 ms
Warning Physical Disk PhysicalDisk1 Max write latency higher than 500 ms