Tag Archives: Get-PSDrive

Return Only the Fixed Disks


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on January 15, 2019.


As a part of a recent engagement (with a company you’ve likely heard of), we had some code written and provided to us. In my review of what was provided to scan drives using Windows Defender, I noticed that there were some problems. One, there was an assumption that a computer would only ever have a single optical drive, and two, mapped network drives may have ended up being scanned, as well. Now, I’m not sure if Defender would actually scan a network drive, but I assume it would and don’t really care to find out.

We needed a way to filter out optical drives and network mapped drives regardless of the count of either before we started a Windows Defender scan. I’ll start with the code I used, followed by a second option that occurred more recently — it was a would this work idea. It does, so I’ll explain them both and perhaps we’ll all be better off seeing two different options. Before we get deeper into this, take a look at the output provided by Get-PSDrive (when piped to Format-Table and the AutoSize parameter). Clearly, it’s found a good number of drives on my system.

PS> Get-PSDrive | Format-Table -AutoSize
Name     Used (GB) Free (GB) Provider    Root                      CurrentLocation
----     --------- --------- --------    ----                      ---------------
Alias                        Alias
C           698.18    232.15 FileSystem  C:\                           Users\tommy
Cert                         Certificate \
D                            FileSystem  D:\
E                            FileSystem  E:\
Env                          Environment
Function                     Function
HKCU                         Registry    HKEY_CURRENT_USER
HKLM                         Registry    HKEY_LOCAL_MACHINE
MDI                          FileSystem  \\mydomain.com\data\in...
MDT                          FileSystem  \\mydomain.com\data\to...
I           112.80    352.96 FileSystem  I:\
P             0.15    465.60 FileSystem  P:\
Variable                     Variable
W           676.59    254.92 FileSystem  W:\
WSMan                        WSMan

Now, let’s modify our command and only return our FileSystem drives. Unfortunately, there are a couple of optical drives (although you don’t really know that yet), and two mapped network drives that we don’t want or need in our results.

PS> Get-PSDrive | Format-Table -AutoSize
Name     Used (GB) Free (GB) Provider    Root                      CurrentLocation
----     --------- --------- --------    ----                      ---------------
Alias                        Alias
C           698.18    232.15 FileSystem  C:\                           Users\tommy
Cert                         Certificate \
D                            FileSystem  D:\
E                            FileSystem  E:\
Env                          Environment
Function                     Function
HKCU                         Registry    HKEY_CURRENT_USER
HKLM                         Registry    HKEY_LOCAL_MACHINE
MDI                          FileSystem  \\mydomain.com\data\in...
MDT                          FileSystem  \\mydomain.com\data\to...
I           112.80    352.96 FileSystem  I:\
P             0.15    465.60 FileSystem  P:\
Variable                     Variable
W           676.59    254.92 FileSystem  W:\
WSMan                        WSMan

In this next example, we’ll remove the mapped drives from our results. In the end, we have our fixed drives and the D:\ and E:\ drives that have no used or free space. Perhaps those are the optical drives and there are no actual disks in either one. Before we move past this example, however, let’s get the results of this command into a variable, as well. The $FixedDrives variable is assigned toward the bottom of the below example.

PS> Get-PSDrive -PSProvider FileSystem | Where-Object -Property Root -notlike '\\*' | Format-Table -Autosize

Name     Used (GB) Free (GB) Provider    Root CurrentLocation
----     --------- --------- --------    ---- ---------------
C           698.18    232.15 FileSystem  C:\      Users\tommy
D                            FileSystem  D:\
E                            FileSystem  E:\
I           112.80    352.96 FileSystem  I:\
P             0.15    465.60 FileSystem  P:\
W           676.59    254.92 FileSystem  W:\

PS> $FixedDrives = Get-PSDrive -PSProvider FileSystem | Where-Object -Property Root -notlike '\\*'
PS> # Noticed we removed Format-Table -- that was _only_ there for the onscreen display.

Now, let’s get a hold of our optical drives. Because we’ll need them in a variable, we’ll go ahead and make that assignment in this next example, as well.

PS> Get-WmiObject -Class Win32_CDROMDrive | Format-Table -AutoSize

Caption                           Drive Manufacturer             VolumeName
-------                           ----- ------------             ----------
ASUS DRW-1814BLT ATA Device       D:    (Standard CD-ROM drives)
ELBY CLONEDRIVE SCSI CdRom Device E:    (Standard CD-ROM drives)

PS> $OpticalDrives = Get-WmiObject -Class Win32_CDROMDrive

Now that that’s done — oh look, it is the D:\ and E:\ drives — let’s run a comparison against the fixed drives and optical drives we’ve returned. Again, this first example is how I rewrote the code that was provided to us. Once we’ve seen this, then we’ll try the comparison I considered over some recent weekend. This example compares the Get-PSDrive‘s Root property (with the backslash removed) against the Get-WmiObject‘s Drive property. If they don’t match, it stays. Otherwise, it’s filtered out. As you’ll see, when we assign and return our new $DrivesToTest variable, we can see that our D:\ and E:\ drives — our optical drives — have been removed. Perfect.

PS> $DrivesToTest = ($FixedDrives.Root).TrimEnd('\') | Where-Object {$OpticalDrives.Drive -notcontains $_}
PS> $DrivesToTest
C:
I:
P:
W:

Let’s use our $FixedDrives and $OpticalDrives variables again, but this time with the Compare-Object cmdlet. This was the additional idea I had to determine if we can simplify things even more. As some have noticed — I have not shied away from the fact that I tend to do things the hard way, the first time. In case it makes a difference, and I hope it doesn’t, Compare-Object was first introduced in PowerShell 3.0.

PS> (Compare-Object -ReferenceObject ($FixedDrives.Root).TrimEnd('\') -DifferenceObject $OpticalDrives.Drive).InputObject
C:
I:
P:
W:

Just like that, we’ve got the same results, with less work. Now, I can get back to doing whatever it was before I began reviewing this code. That and our users can safely get to work scanning machines with Windows Defender, without the concern anyone will scan against any number of optical drives, or mapped network drives.

Show-PSDriveMenu Revisited


Welcome to the 299th post on tommymaynard.com. It’s countdown to 300!


Back on August 14, 2018, I introduced a new utility I had written and shared via the PowerShell Gallery. It was called Show-PSDriveMenu and here’s a quick example of its usage. It lists the currently available drives, includes a marker to indicate the current drive, and allows you to change drives by entering a corresponding drive number.

The problem with the 1.0 version is that when Show-PSDriveMenu was invoked the initial time, it wouldn’t include the marker to indicate the current drive. Well, now it’s included on all the invocations, to include the very first one.

The updated script be downloaded from the PowerShell Gallery using the Install-Script command, included in the PowerShellGet module, using the below example. Be sure to include the Force parameter if you installed the 1.0 version, in order that the older version is replaced by the newer version.

PS > Install-Script -Name Show-PSDriveMenu -Scope CurrentUser

As mentioned in the original post, if you want this function (downloaded as a script file from the PowerShell Gallery), to load each time you start PowerShell, then add one of the two lines of code below to your profile script. The first is for Windows PowerShell (version 5.1 and earlier), and the second is for PowerShell Core (version 6.0 and later).

. $env:USERPROFILE\Documents\WindowsPowerShell\Scripts\Show-PSDriveMenu.ps1
. $env:USERPROFILE\Documents\PowerShell\Scripts\Show-PSDriveMenu.ps1

It’s Available: Show-PSDriveMenu


Welcome to the 295th post on tommymaynard.com. It’s countdown to 300!


Update: There’s a updated post here.

It was only yesterday that I shared something I was writing in PowerShell. It was my take on Get-PSDrive that included a built-in way to change locations, within the file system, by entering the number that corresponds to a drive location. Take a look at the Tweet (and yes, I’m aware of my misspelling).

There was definitely some interest on Twitter. Because of that, I set out to correct at least a couple errors that needed a fix. For now, it’s up and available on the PowerShell Gallery at https://www.powershellgallery.com/packages/Show-PSDriveMenu/1.0/DisplayScript. Use the PowerShellGet, Install-Script function to install it.

PS > Install-Script -Name Show-PSDriveMenu -Scope CurrentUser

Here’s an image from the ConsoleHost. There’s a small visual change that’s been added since the above Tweet. Numeric values with one digit (1-9) have an extra space in front of the digit in order that everything lines up well.

Update: It appears that there may be a problem at first launch, as the marker that indicates the current drive (>), doesn’t appear. It does, however, on subsequent uses. I’ll have to take a look at that at some point and see if it can be isolated and resolved. If you catch it (and, seriously there’s a minimal amount of code in this function), feel free to let me know.

Update: If you want this function (downloaded as a script file from the PowerShell Gallery), to load each time you start PowerShell, then add one of the two lines of code below. The first is for Windows PowerShell (version 5.1 and earlier), and the second is for PowerShell Core (version 6.0 and later). You know, when Windows PowerShell and PowerShell began to mean different things.

. $env:USERPROFILE\Documents\WindowsPowerShell\Scripts\Show-PSDriveMenu.ps1
. $env:USERPROFILE\Documents\PowerShell\Scripts\Show-PSDriveMenu.ps1

Keep PowerShell Cmdlets Powerful

I’ve seen what I’m about to show you done repeatedly in Windows PowerShell. With that thought in my mind, I’ve decided it’s time to officially write about it. The first time I noticed someone doing it, well, it was me. I did, however, write a correction to my team so they knew I was aware of my error, and as a hope they wouldn’t repeat it.

Those doing this may be new to PowerShell, but if you aren’t careful and don’t fully understand the capabilities of the cmdlets you use, then it’s possible you might make this mistake, too. What am I talking about? I’m talking about Invoke-Command and an improper way that it’s sometimes used.

Invoke-Command‘s purpose is to run a command, or commands, on remote computers and return the results back to the local computer. That said, it can be used on the local computer, as well; however, this is usually only necessary to check if PowerShell Remoting is working on the local computer. While I don’t normally do this in my examples, I’m piping my result of this example to Format-Table -AutoSize once they’re returned from the remote computer. This is so it’ll display better on this webpage.

Invoke-Command -Computer DC01 -ScriptBlock {Get-PSDrive -PSProvider FileSystem} | Format-Table -AutoSize
Name Used (GB) Free (GB) Provider Root              CurrentLocation PSComputerName
---- --------- --------- -------- ----              --------------- --------------
A                                 A:\                               DC01
C        12.91     26.75          C:\  Users\tommymaynard\Documents DC01
D                                 D:\                               DC01

This example of Invoke-Command connected to the remote computer DC01, ran a filtered Get-PSDrive command, returned the results to my computer, and then were formatted by the Format-Table cmdlet.

Now, let’s say we have… 23 computer names stored in the variable $Computers and we want to run the command we used above, against each computer. What so many people seem to do is wrap Invoke-Command in a Foreach construct (think: loop). I’m guessing, because I’ve made the error myself, that it’s because someone learned about Foreach first. Like I said, I discovered my error on my own, and only moments later. It didn’t wake me at 2 a.m. in a cold sweat after two months, which could’ve been possible.

Here’s how not to do this, and again, $Computers is holding the names of 23 computers.

Foreach ($Computer in $Computers) {
    Invoke-Command -ComputerName $Computer -ScriptBlock {Get-PSDrive -PSProvider FileSystem}
}
Name           Used (GB)     Free (GB) Provider      Root                                CurrentLocation PSComputerName
----           ---------     --------- --------      ----                                --------------- --------------
C                  20.47         59.18               C:\                                 ...rd\Documents DC02
D                                                    D:\                                                 DC02
A                                                    A:\                                                 WEB01
C                  50.36         29.30               C:\                                 ...rd\Documents WEB01
D                                                    D:\                                                 WEB01
A                                                    A:\                                                 SQL01
C                  53.59         26.06               C:\                                 ...rd\Documents SQL01
D                                                    D:\                                                 SQL01
... # Not all results included.

What we’ve done here is force Invoke-Command to run against only one computer at a time. During each iteration of the Foreach loop, Invoke-Command runs against the computer name currently stored in $Computer. This variable is updated to the next computer name in the variable at the start of each loop. It’s like this: connect to DC02 and run the command. Now, connect to WEB01 and run the command. Are you done, Invoke-Command? Okay then, now Foreach says to run the same command against the computer SQL01. That’s not how this cmdlet was designed to be used.

Before we go any further, I wrapped this command inside the Measure-Command cmdlet to determine how long it took my system to run this against the computers in $Computers. It took nearly 30 seconds. While that’s not an eternity, we’re only talking about 23 computers, and we’re only talking about a single command.

Measure-Command -Expression {
    Foreach ($Computer in $Computers) {
        Invoke-Command -ComputerName $Computer -ScriptBlock {Get-PSDrive -PSProvider FileSystem}
    }
}
Days : 0
Hours : 0
Minutes : 0
Seconds : 27
Milliseconds : 513
Ticks : 275132689
TotalDays : 0.000318440612268519
TotalHours : 0.00764257469444444
TotalMinutes : 0.458554481666667
TotalSeconds : 27.5132689
TotalMilliseconds : 27513.2689

Now, let’s set this command up the proper way. The ComputerName parameter of Invoke-Command can take a collection of computers. It should be said that many cmdlets can take more than one computer as a value to their ComputerName parameter. The difference is that Invoke-Command can run against all of the 23 computers at the same time. In fact, the default throttle limit—the number of computers in which the command will run against simultaneously—is 32. It can be changed by including the ThrottleLimit parameter, such as -ThrottleLimit 50 or -ThrottleLimit 15.

Now, here’s how this command should’ve been written.

Invoke-Command -ComputerName $Computers -ScriptBlock {Get-PSDrive -PSProvider FileSystem}
Name           Used (GB)     Free (GB) Provider      Root                                CurrentLocation PSComputerName
----           ---------     -------u-- --------      ----                                --------------- --------------
A                                                    A:\                                                 DC02
A                                                    A:\                                                 WEB01
C                  41.72         37.93               C:\                                 ...rd\Documents WEB01
C                  41.13         38.52               C:\                                 ...rd\Documents DC02
D                                                    D:\                                                 DC02
D                                                    D:\                                                 WEB01
A                                                    A:\                                                 SQL01
C                  46.75         32.91               C:\                                 ...rd\Documents SQL01
D                                                    D:\                                                 SQL01
... # Not all results included.

I measured this command five times, and the average of those five runs was only 2.8 seconds. Remember, the Foreach loop took almost 30 seconds to get the same results. This is due to the fact that, again, Invoke-Command will work with the remote computers simultaneously (when it’s not inside a Foreach). There’s never any waiting to run the command on any additional computers unless there’s more computers than the value of ThrottleLimit.

Please keep the topic in this post in mind, as it’s possible to momentarily forget this feature just long enough to wrap the Invoke-Command cmdlet in a Foreach, or other looping construct.