Tag Archives: calculated properties

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.

Return File Sizes in Bytes, KBs, MBs, and GBs, at the Same Time

I scanned a recent forum post on PowerShell.org where the user seemed (again, I scanned it) to want to run some files through Get-ChildItem and Select-Object, and report the size in something other than the bytes default. It didn’t appear he, or she, wanted all the files in one of the measurement groups: B, KB, MB, or GB, but instead in their proper measurement group, dependent on the file’s size.

That may be hard to understand at first read, so let me try and explain it another way. If a file has less than 1,024 bytes, then it should be reported in bytes, if it has 1,024 – 1,048,575 bytes, then it should be reported in Kilobytes, if it has 1,048,576 – 1,073,741,824 bytes then it should be reported Megabytes, and if it has 1,073,741,825 or more bytes, then it should be reported in Gigabytes.

I wrote a long, “one-liner” to do this and have included it, and some sample output, below. I don’t profess to guarantee this doesn’t have any errors, so if you find some, then let me know. In addition, it shows a nice example of a switch statement inside the Expression portion of a calculated property. Fancy.

Get-ChildItem -Path 'C:\test' |
    Select-Object Name,
        @{L='Size';E={
                switch ($_.Length) {
                    # Bytes
                    {$_ -eq 0} {"$('{0:N2}' -f $_) bytes"; break}
                    {$_ -eq 1} {"$('{0:N2}' -f $_) byte"; break}
                    {($_ -gt 1) -and ($_ -le 1023)} {"$('{0:N2}' -f $_) bytes"; break}

                    # Kilobytes
                    {$_ -eq 1024} {"$('{0:N2}' -f ($_/1KB)) Kilobyte"; break}
                    {($_ -gt 1024) -and ($_ -le 1048575)} {"$('{0:N2}' -f ($_/1KB)) Kilobytes"; break}

                    # Megabytes
                    {$_ -eq 1048576} {"$('{0:N2}' -f ($_/1MB)) Megabyte"; break}
                    {($_ -gt 1048576) -and ($_ -le 1073741824)} {"$('{0:N2}' -f ($_/1MB)) Megabytes"; break}

                    # Gigabytes
                    {$_ -eq 1073741825} {"$('{0:N2}' -f ($_/1GB)) Gigabyte"; break}
                    {$_ -gt 1073741825} {"$('{0:N2}' -f ($_/1GB)) Gigabytes"; break}

                    default {Write-Warning -Message 'Unknown Error.'}
                }
            }
        } | Format-Table -AutoSize

Name           Size
----           ----
My-A-File.txt  1.00 byte
My-A-File2.txt 445.00 bytes
My-B-File.txt  1.00 Kilobyte
My-B-File2.txt 1.30 Kilobytes
My-C-File.txt  2.09 Megabytes
My-D-File.txt  3.59 Gigabytes

I can easily see how someone might want to remove the singular vs. plural: Kilobyte vs. Kilobytes, and just use B, KB, MB, and GB, and so…

Update: I gave this some more thought today, and I really didn’t feel like it was okay to not include a modified version that uses abbreviations (B, KB, MB, GB) instead of the full words, as in the previous example. It has removed some complexity, as well, as you can see below.

Get-ChildItem -Path 'C:\test' |
    Select-Object Name,
        @{L='Size';E={
                switch ($_.Length) {
                    # Bytes
                    {($_ -ge 0) -and ($_ -le 1023)} {"$('{0:N2}' -f $_) B"; break}

                    # Kilobytes
                    {($_ -ge 1024) -and ($_ -le 1048575)} {"$('{0:N2}' -f ($_/1KB)) KB"; break}

                    # Megabytes
                    {($_ -ge 1048576) -and ($_ -le 1073741824)} {"$('{0:N2}' -f ($_/1MB)) MB"; break}

                    # Gigabytes
                    {$_ -ge 1073741825} {"$('{0:N2}' -f ($_/1GB)) GB"; break}

                    default {Write-Warning -Message 'Unknown Error.'}
                }
            }
        } | Format-Table -AutoSize

Name           Size
----           ----
My-A-File.txt  1.00 B
My-A-File2.txt 445.00 B
My-B-File.txt  1.00 KB
My-B-File2.txt 1.30 KB
My-C-File.txt  2.09 MB
My-D-File.txt  3.59 GB

Using Replace() to Fix Split() (and Convert-Path)

I was working on a recent project that required scripting ACLs, and so I had a Windows PowerShell console open in addition to the ISE. This allowed me to quickly check the owner of a directory (or folder). I could press the up arrow to rerun my command, and I could quickly see if the owner had changed according to my script. Let’s say my directory is called ‘TestFolder’ and is located at the root of the C:\ drive.

PS C:\> New-Item -Path TestFolder -ItemType Directory

    Directory: C:\

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        10/21/2014   9:19 PM            TestFolder

To get the ACL information for the folder, you can use the Get-Acl cmdlet, as in the example below.

PS C:\> Get-Acl -Path .\TestFolder

    Directory: C:\

Path                                    Owner                                   Access
----                                    -----                                   ------
TestFolder                              BUILTIN\Administrators                  BUILTIN\Administrators Allow  FullCo...

Being the PowerShell enthusiast that I am, I modified my command so that only the information I wanted (the path and the owner) was being returned. This is done using the Select-Object cmdlet. Unfortunately, when the command returned those two properties, the Path property was no longer what I was expecting – take a look below. While the example above only returned the name of the folder (TestFolder), I thought I would change this to show the full path (C:\TestFolder) since I was now dealing with a string that included it.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property Path,Owner

Path                                                        Owner
----                                                        -----
Microsoft.PowerShell.Core\FileSystem::C:\TestFolder         BUILTIN\Administrators

The first thing I thought to do was to split the path at the two colons (::) and grab the second element, which I thought would end up being C:\TestFolder. The split method didn’t work so well; here’s what I ended up with.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property @{N='Path';E={($PSItem.Path).Split('::')[-1]}},Owner

Path                                                        Owner
----                                                        -----
\TestFolder                                                 BUILTIN\Administrators

The reason this didn’t work is because the Split() method doesn’t split on each occurrence of two, back-to-back colons like you might expect, it splits on every colon. Since C:\ has a colon, it split there as well. FYI: The use of [-1] returns the last element in an array – good to know, I know. Here’s an example that may help better explain. In this example below, the string is split on every exclamation point (!) and every question mark (?) – not only on the combination of both (!?).

PS C:\> $String = 'Today is the 21st! That is great news, right?'
PS C:\> $String
Today is the 21st! That is great news, right?
PS C:\> $String.Split('!?')
Today is the 21st
 That is great news, right

PS C:\>

What I then decided to do to get this the way I wanted it, was to first replace the two, back-to-back colons with a single character (that was not a part of the string), and then split on that single character. It worked, and here’s what that looks like.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property @{N='Path';E={(($PSItem.Path).Replace('::','@')).Split('@')[-1]}},Owner

Path                                                        Owner
----                                                        -----
C:\TestFolder                                               BUILTIN\Administrators

PS C:\>

It was about this point, that I wanted to see if the -split operator would have handled this the same way and required the additional work that the Replace()/Split() methods did. Of course, after all I did to get this to work how I wanted, I determined I should have started with the -split operator. The -split operator isn’t looking at the characters individually, but instead of, as a whole – two, back-to-back colons is two back-to-back colons.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property @{N='Path';E={(($PSItem.Path) -split '::')[-1]}},Owner

Path                                                        Owner
----                                                        -----
C:\TestFolder                                               BUILTIN\Administrators

A note, if the @{N=…;E={…}} syntax is confusing, or new to you, then spend some time reading this: http://technet.microsoft.com/en-us/library/ff730948.aspx, and then run Get-Help about_Hash_Tables.

This happens just about every time I get to ready to publish a new post. I discovered a better way to handle the problem – much like I did when I considered the -split operator.  It turns out that I could have used a built-in cmdlet to convert the path for me. That’s right, there’s a cmdlet that would have handled everything. Using the Convert-Path cmdlet will convert something like this: Microsoft.PowerShell.Core\FileSystem::C:\TestFolder to this: C:\TestFolder. Here’s the example.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property @{N='Path';E={Convert-Path $PSItem.Path}},Owner

Path                                                        Owner
----                                                        -----
C:\TestFolder                                               BUILTIN\Administrators

PS C:\>

Well, that’s it for this one. As much as I might seem irritated about how I did this three different ways – from the most work to the least – I understand how important this learning process is, and that one day I will be grateful for having gone down this path… (pun intended).