Tag Archives: Compare-Object

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.

An Enhanced Active Directory Group Membership Compare

Like everyday, there was a Windows PowerShell question on the Internet today. While I may not have provided everything the original poster was asking for, I rather like what I had accomplished. Being that I have a website that resolves around PowerShell 100% of the time, it seemed like a good little chunk of code to share. I’m going to want it one day, I just know it, and I’m not going to want to rewrite it.

What we’ve done is compared the Active Directory (AD) group memberships of two different users using Get-ADPrincipalGroupMembership and the Compare-Object cmdlet. Stay tuned, this isn’t the standard Compare-Object output you might be used to seeing. I’m doing this much like the original forum question, so l stored the SamAccountName of two AD users, in two different variables.

PS> $FirstUser = 'tommymaynard'
PS> $SecondUser = 'lanceandrews'

With the variables set, we’ve used them as part of our Get-ADPrincipalGroupMembership commands below. In this example, you can see two ways of accomplishing the same thing — useful stuff, really.

PS> $FirstUserGroups = Get-ADPrincipalGroupMembership $FirstUser | Select-Object -ExpandProperty Name
PS> $SecondUserGroups = (Get-ADPrincipalGroupMembership $SecondUser).Name

With this second set of variables assigned, we’ve set up the command below to obtain the differences and store those in a variable we’ve called $Difference. Including the -IncludeEqual parameter has allowed us to be able to see the group memberships that both users share.

PS> $Difference = Compare-Object -ReferenceObject $FirstUserGroups -DifferenceObject $SecondUserGroups -IncludeEqual

Next, we put together some calculated properties from our $Difference variable. If calculated properties are new to you, be sure to do some additional research. They can be used to simply rename properties, but also, like I’ve demonstrated below, they can include conditional logic. You might consider copying and pasting the example somewhere else, so you can better digest what’s happening. We’ve created three calculated properties: the first is renaming the InputObject property to Group. The next two properties write one of two things: either that the user is a member, or that they’re not, by entering two, side-by-side dashes. See the image further below.

PS> $Difference | Select-Object @{N='Group';E={$_.InputObject}},@{N='First User';E={If ($_.SideIndicator -eq '<=' -or $_.SideIndicator -eq '==') {'Member'} Else {'--'}}},@{N='Second User';E={If ($_.SideIndicator -eq '=>' -or $_.SideIndicator -eq '==') {'Member'} Else {'--'}}}

This modification of the standard Compare-Object output reminds us which user is which. The standard output uses arrows. A left arrow indicates the first user is a member and a right arrow indicates the second user is a member. Because we’re using the -IncludeEqual parameter, we can distinguish when both users are a member of the same AD group, and that, without the double equal sign, as the indicator.

compare-active-directory-membership-between-two-users01

Neat. I’ll be back to visit you later, modified Compare-Object command. Thanks for reading, everyone.

Compare Two Arrays for Differences

I realized how obsessed I am with much I appreciate Windows PowerShell today, as I was working with Exchange 2013. I needed to find the differences in access rights between a PublishingEditor and Editor. The image below, pulled from a Microsoft web page, lists the different access rights.

Compare Two Arrays for the Differences01

One way to determine the difference would be to sit and stare at the screen. Nah. It didn’t take my eyes long, darting back and forth between lines, to desire a better way. Another way would be to take a screen capture and mark off the common entries. The image below shows what that looks like. Seriously, though, I can do better. Let’s do this in PowerShell and give ourselves a little practice. More on that in a moment.

Compare Two Arrays for the Differences02

I copied and pasted the first line I wanted to my PowerShell console.

PS> PublishingEditor   CreateItems, ReadItems, CreateSubfolders, FolderVisible, EditOwnedItems, EditAllItems, DeleteOwnedItems, DeleteAllItems

Then, I bounced around the entry to ensure that $PublishingEditor became a variable, by adding a dollar sign. I also added the =  assignment operator and put single quotes around the entire group of access rights, but this wasn’t quite enough.

PS> $PublishingEditor = 'CreateItems, ReadItems, CreateSubfolders, FolderVisible, EditOwnedItems, EditAllItems, DeleteOwnedItems, DeleteAllItems'

The next thing I did was add a couple methods — one right after the other, at the end of the command. The first method I added was .Replace(). In the version I added, .Replace(‘ ‘,”), it replaces all single spaces with nothing, effectively removing the spaces. The next method I added was .Split(). This, when added as .Split(‘,’), splits the string at each comma.

PS> $PublishingEditor = 'CreateItems, ReadItems, CreateSubfolders, FolderVisible, EditOwnedItems, EditAllItems, DeleteOwnedItems, DeleteAllItems'.Replace(' ','').Split(',')

Next, I copied the second line, for Editor access rights, pasted it in the console and cleaned it up, too.

PS> $Editor = 'CreateItems, ReadItems, FolderVisible, EditOwnedItems, EditAllItems, DeleteOwnedItems, DeleteAllItems'.Replace(' ','').Split(',')

At this point, I had two variables, $PublishingEditor and $Editor. I needed a way to compare the two variables to determine which one had additional access rights (presumably PublishingEditor) and what those were.

The first thing I did was echo the variables’ values to my screen. The second, was to run a comparison of the two. This was done using the Compare-Object cmdlet, where I provided one variable, $PublishingEditor, as the value for the -ReferenceObject parameter, and the other variable, $Editor, as the value for the -DifferenceObject parameter.

PS> $PublishingEditor
CreateItems
ReadItems
CreateSubfolders
FolderVisible
EditOwnedItems
EditAllItems
DeleteOwnedItems
DeleteAllItems
PS>
PS> $Editor
CreateItems
ReadItems
FolderVisible
EditOwnedItems
EditAllItems
DeleteOwnedItems
DeleteAllItems
PS>
PS> Compare-Object -ReferenceObject $PublishingEditor -DifferenceObject $Editor

InputObject                                                 SideIndicator
-----------                                                 -------------
CreateSubfolders                                            <=

The results, above, indicate that $PublishingEditor (the InputObject) has an additional access right, called CreateSubfolders. As you can see, the default results of Compare-Object only show the differences. Had I used the -IncludeEqual parameter the results would have looked like the example below.

PS> Compare-Object -ReferenceObject $PublishingEditor -DifferenceObject $Editor -IncludeEqual

InputObject                                                 SideIndicator
-----------                                                 -------------
CreateItems                                                 ==
ReadItems                                                   ==
FolderVisible                                               ==
EditOwnedItems                                              ==
EditAllItems                                                ==
DeleteOwnedItems                                            ==
DeleteAllItems                                              ==
CreateSubfolders                                            <=

Thanks for reading. Now off to find some other way to use PowerShell to speed up my day.

Before I do that, though, I mentioned practice earlier in this post. To really get PowerShell, we need to take every opportunity to continue to learn, but also to practice what we already know. This was a perfect opportunity. Not only would I get the results I needed — the difference in access rights — but I also got a half of minute of working with the Compare-Object cmdlet and the .Replace() and .Split() methods, where I may not have otherwise.

The more I use PowerShell, the less I have to try and remember when I really need it.

Compare Roles and Feature between Servers

We have many different ways to build our servers so they are, and stay, the same: Desired State Configuration, is one way. In case you’re not there yet (and, I’m not, entirely), you may need to compare the roles and features between your servers to check for any differences.

In this first example, we’ll create two variables to store the installed roles and features of two different servers. Keep in mind that I’m working from a Windows 8.1 client, and two Server 2012 R2 servers. You’ll need to make sure you’re using these versions, as the Get-WindowsFeature cmdlet in 8.1 returns an error when trying to connect to a 2008 R2 server. As well, some of the Roles and Features’ names have changed. Therefore, I decided to only focus on 2012 R2.

PS> $DC01 = Get-WindowsFeature -Computer DC01 | Where-Object InstallState -eq Installed
PS> $NewDC = Get-WindowsFeature -Computer NewDC | Where-Object InstallState -eq Installed

Now that we have our populated variables, we can do our comparison between the two on their Name properties.

PS> Compare-Object -ReferenceObject $DC01.Name -DifferenceObject $NewDC.Name

InputObject                                                 SideIndicator
-----------                                                 -------------
AD-Domain-Services                                          <=
DNS                                                         <=
RSAT-DNS-Server                                             <=
RSAT-File-Services                                          <=
RSAT-DFS-Mgmt-Con                                           <=

If you want to view the roles and features that are installed on both servers, then include the -IncludeEqual parameter on Compare-Object, such as in the example below.

PS> Compare-Object -ReferenceObject $DC01.Name -DifferenceObject $NewDC.Name -IncludeEqual

InputObject                                                 SideIndicator
-----------                                                 -------------
NET-Framework-Features                                      ==
NET-Framework-Core                                          ==
NET-Framework-45-Features                                   ==
NET-Framework-45-Core                                       ==
NET-WCF-Services45                                          ==
NET-WCF-TCP-PortSharing45                                   ==
AD-Domain-Services                                          <=
DNS                                                         <=
RSAT-DNS-Server                                             <=
RSAT-File-Services                                          <=
RSAT-DFS-Mgmt-Con                                           <=

What might not be immediately evident for some, is that we could have skipped setting the first two variables and only used a single Compare-Object command. It can retrieve the information from each server and then compare it, at nearly the same time. We’ve remove the -IncludeEqual parameter is this example.

Compare-Object -ReferenceObject ((Get-WindowsFeature -ComputerName DC01 | Where-Object InstallState -eq Installed).Name) -DifferenceObject ((Get-WindowsFeature -ComputerName NewDC | Where-Object InstallState -eq Installed).Name)

InputObject                                                 SideIndicator
-----------                                                 -------------
AD-Domain-Services                                          <=
DNS                                                         <=
RSAT-DNS-Server                                             <=
RSAT-File-Services                                          <=
RSAT-DFS-Mgmt-Con                                           <=

Based on these results, and the ones further above, we have determined that our reference computer, DC01, has five additional packages installed.