Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Extending PowerShell PSDrives

Posted on September 23, 2021September 23, 2021

Yesterday I shared some PowerShell code I use to managing my PSDrive assignments. My code works for me in my environment. But that doesn't mean it is necessarily right for you and your environment. There are plenty of ways to use PowerShell to achieve the same results as my code. This is something you should always keep in mind when looking at someone else's code. But enough caveats. In the previous post I left you with a tease that there is more to be done with PSDrives. Let's look at that today.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

Format Extensions

By now, you should know that I am a big fan of custom formatting. It saves time and allows me to see information in a format that meets a particular need. Formatting extensions require an XML file, but if you've been reading me for a while, you know about my New-PSFormatXML function that is part of the PSScriptTools module. I used this command to add a few custom views for PowerShell PSDrives.

When you run Get-PSDrive, PowerShell formats the results using a default view named Drive. I added a couple of new table views. One of them displays PSDrives grouped by provider.

In order to group properly, you need to remember to sort on the Provider property. Or get a bit more granular with Get-PSDrive.

The Info view is similar but doesn't include the provider.

Get-FileSystemDrive

As I was working on these extensions, I realized the FileSystem PSDrives are of more value to me. One thing that has always annoyed me is that I get size and usage information, even for PSDrives that point to folders.

I wanted something better. This was going to mean more than a format extension. I wrote my own command.

#requires -version 5.1

Function Get-FileSystemDrive {
    [cmdletbinding()]
    [OutputType("PSFileSystemDrive")]
    [alias("gfsd")]
    Param(
        [Parameter(HelpMessage = " Specifies, as a string array, the name or name of FileSystem PSDrives. Type the drive name or letter without a colon (:)")]
        [string[]]$Name
    )
    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
    } #begin

    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting FileSystem PSDrives"
        Try {
            $drives = Get-PSDrive @PSBoundParameters -PSProvider FileSystem -ErrorAction Stop
        }
        Catch {
            Throw $_
        }

        #create a custom object from the drive information
        if ($drives) {
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Found $($drives.count) FileSystem PSDrives"
            foreach ($drive in $drives) {
                #only get size information for root drives
                #need to account for non-Windows systems
                if ($drive.root -match "^(([A-Za-]:)\\|\/)$") {
                    $info = [system.io.driveinfo]::new($drive.root)
                    if ($info.DriveType -eq 'Fixed') {
                        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting root drive detail for $($info.name)"
                        $Free = $info.AvailableFreeSpace
                        $Used = $info.TotalSize - $info.AvailableFreeSpace
                        $Format = $info.DriveFormat
                        $Size = $info.totalSize
                        $IsPhysical = $True
                    }
                    else {
                        $Free = $null
                        $Used = $null
                        $Format = $Null
                        $Size = $null
                        $IsPhysical = $False
                    }
                }
                else {
                    $Free = $null
                    $Used = $null
                    $Format = $Null
                    $Size = $Nuyll
                    $IsPhysical = $False
                }
                [PSCustomObject]@{
                    PSTypeName      = "PSFileSystemDrive"
                    Name            = $drive.Name
                    Root            = $drive.Root
                    CurrentLocation = $drive.CurrentLocation
                    Description     = $drive.Description
                    Free            = $free
                    Used            = $used
                    Size            = $Size
                    Format          = $Format
                    IsPhysical      = $IsPhysical
                }
            }
        }

    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"

    } #end

} #close Get-FileSystemDrive

The function is a wrapper for Get-PSDrive that only returns FileSystem drives. For each drive, I create my own custom object using a typename of PSFileSystemDrive. I need a unique typename for custom format and type extensions that are coming. The custom object makes it easier to differentiate between root physical drives like C: and D: from a mapping like T which goes to C:\Training.

In the same file as the function, I also define a custom type extension to define a script property.

#add a script property to the custom object
$value = {
    if ($this.IsPhysical) {
        ($this.used/$this.size)*100
    }
    else {
        $Null
    }
}
Update-TypeData -TypeName 'PSFileSystemDrive' -MemberType ScriptProperty -MemberName Utilization -Value $value -force

If the PSDrive is a physical drive, I'm calculating the utilization percentage. I could have made this a static property when I defined the object. And yes, I could have defined my custom object as a PowerShell class. Which I still might do because I am getting other ideas as I write this. But for now, this is what I have.

I also used New-PSFormatXML to define a default formatted view.

I find this output to be more concise and meaningful. Notice that I only get size and usage information for physical drives. My format ps1xml file also uses scriptblocks and ANSI sequences to display usage percentages in color. If the percentage is 90 or above it will be shown in red. Although, if you are using Windows Terminal, the colors may be slightly different depending on your color scheme.

I also created a second view called Status, which works best with physical drives.

This view doesn't have the ANSI highlighting for utilization, although you could certainly add it.

All of the type extensions are in the same file.

<!--
Format type data generated 09/22/2021 09:13:30 by PROSPERO\Jeff

This file was created using the New-PSFormatXML command that is part
of the PSScriptTools module.

https://github.com/jdhitsolutions/PSScriptTools
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 09/22/2021 09:13:30 by PROSPERO\Jeff-->
      <Name>provider</Name>
      <ViewSelectedBy>
        <TypeName>System.Management.Automation.PSDriveInfo</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <ScriptBlock>$_.provider.Name</ScriptBlock>
        <Label>Provider</Label>
      </GroupBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.
        <AutoSize />-->
        <TableHeaders>
          <TableColumnHeader>
            <Label>Name</Label>
            <Width>12</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Root</Label>
            <Width>45</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>CurrentLocation</Label>
            <Width>20</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Description</Label>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <Wrap />
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Root</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>CurrentLocation</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Description</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <!--Created 09/22/2021 11:15:30 by PROSPERO\Jeff-->
      <Name>info</Name>
      <ViewSelectedBy>
        <TypeName>System.Management.Automation.PSDriveInfo</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.-->
        <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
            <Label>Name</Label>
            <Width>7</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Root</Label>
            <Width>7</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>CurrentLocation</Label>
            <Width>18</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Description</Label>
            <Width>14</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <Wrap />
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Root</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>CurrentLocation</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Description</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <!--Created 09/22/2021 10:02:46 by PROSPERO\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>PSFileSystemDrive</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.-->
        <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
            <Label>Name</Label>
            <Width>7</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Root</Label>
            <Width>7</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Current</Label>
            <Width>18</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>SizeGB</Label>
            <Width>15</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>PctUsed</Label>
            <Width>21</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Format</Label>
            <Width>9</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Root</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>CurrentLocation</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                  if ($_.size) {
                    $_.Size/1GB -AS [int]
                  }
                  else {
                    $null
                  }
                  </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                 if ($_.Utilization) {
                    <!-- display using ANSI-->
                    if ($host.name -match "console|code|ServerRemoteHost") {
                      $value = [math]::Round($_.Utilization,2)
                      Switch ($value) {
                        {$_ -ge 90} {
                          <!--red-->
                          "$([char]27)[90m$value$([char]27)[0m"
                        }
                        {$_ -ge 75} {
                          <!--yellow-->
                          "$([char]27)[38;5;227m$value$([char]27)[0m"
                        }
                        {$_ -ge 50} {
                          <!--purple-->
                          "$([char]27)[95m$value$([char]27)[0m"
                        }
                        default {
                          <!--green-->
                          "$([char]27)[92m$value$([char]27)[0m"
                        }
                      }
                    }
                    else {
                     [math]::Round($_.Utilization,2)
                    }
                  }
                  else {
                    $null
                  }
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Format</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <!--Created 09/22/2021 12:39:34 by PROSPERO\Jeff-->
      <Name>status</Name>
      <ViewSelectedBy>
        <TypeName>PSFileSystemDrive</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <ScriptBlock>
        <!--display name using ANSI if supported-->
        if ($host.name -match "console|code|ServerRemoteHost") {
          $n = "$([char]27)[92m$($_.Name)$([char]27)[0m"
        }
        else {
          $n = $_.name
        }
        "{0}`n   Root: {1}" -f $n,$_.Root
        </ScriptBlock>
        <Label>Name</Label>
      </GroupBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.-->
        <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
            <Label>CurrentLocation</Label>
            <Width>18</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>SizeGB</Label>
            <Width>7</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>FreeGB</Label>
            <Width>7</Width>
            <Alignment>Right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Utilization</Label>
            <Width>20</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Description</Label>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <Wrap />
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>CurrentLocation</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                if ($_.IsPhysical) {
                  $_.Size/1GB -as [int]
                }
                else {
                  $null
                }
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                if ($_.IsPhysical) {
                  $_.Free/1GB -as [int]
                }
                else {
                  $null
                }
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>
                if ($_.IsPhysical) {
                  [math]::Round($_.Utilization,2)
                  }
                else {
                  $null
                }
                </ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Description</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

In the file that defines the function, I add this snippet:

Update-FormatData $psscriptroot\PSDriveInfo.format.ps1xml

All of the files are in the same directory which is why I am using $PSScriptRoot.

Now I have commands and options that make my work easier. Sure, I can still use a command like Get-Volume. Or I can use my own command (which has a defined alias).

I hope you'll grab the code and play with it. That is a great way to improve your PowerShell scripting. Have fun!


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d