Monthly Archives: November 2015

Search PowerShell Gallery Module for #Requires Statement

The first part of this post began on PowerShell.org. Start there, unless you already have: http://powershell.org/wp/2015/11/30/search-powershell-gallery-module-for-requires-statement.

Please use the connect.microsoft.com link here, or at the bottom of this post, to up vote my feedback.

Desired State Configuration (DSC) and Just Enough Administration (JEA) are two topics that have recently piqued my interest. This, after becoming involved in Windows PowerShell constrained endpoints, and writing proxy functions, to better control how cmdlets are used. While working with DSC and xJEA (x, as in experimental), I ran up against an error message in the Event Viewer on my Server 2012 R2 target node running Windows Management Framework (WMF) 4.0 (the installation package that contains PowerShell 4.0). Before I get too deeply involved in JEA in WMF 5.0 on Server 2016 — what appears to be constrained endpoints of the past, with the benefits of JEA, but without the need for DSC, and some additional new features — I wanted to ensure I was able to deploy JEA endpoints with DSC, while running WMF 4.0.

Knowing this, the first error message below, makes perfect sense. I downloaded a version of xJEA that had a PowerShell 5.0 requirement, without even knowing it. Instead of moving up to WMF 5.0 to “fix” this, I opted to first get an older version of xJEA and determine if I was able to make it run with a version that can be used on WMF 4.0. This seemed like a logical progression in my learning and understanding of both DSC and JEA. Here’s the error:

“Error Message = The script ‘MSFT_xJeaToolkit.psm1’ cannot be run because it contained a “#requires” statement for Windows PowerShell 5.0. The version of Windows PowerShell that is required by the script does not match the currently running version of Windows PowerShell 4.0.”

As to be expected, the error message that was reported in the console host, was much less helpful. In fact, I was at a total loss until I went hunting in the Event Viewer, where I turned up the previous error message. Here’s the error I saw in the console:

“Invoke-CimMethod : Failed to extract the module from zip file C:\Windows\TEMP\\635842583367244849\xJea_0.2.16.6.zip
downloaded by Download Manager WebDownloadManager.”

In order to determine which version of xJEA didn’t require PowerShell 5.0, I needed to download all the xJEA versions and inspect them. You see, there isn’t a way to find out if there’s a PowerShell version requirement… more on that shortly. To begin, I ensured that the PowerShell Gallery was a trusted repository, and therefore, wouldn’t prompt me when I ran the Save-Module command in an upcoming example. To do this, I ran the commands below to (1) verify if the PowerShell Gallery was trusted (which it wasn’t, and isn’t by default), (2) trust it as an installation source, and (3) verify it was trusted.

PS> Get-PSRepository

Name                      PackageManagementPro InstallationPolicy   SourceLocation
                          vider
----                      -------------------- ------------------   --------------
PSGallery                 NuGet                Untrusted            https://www.powershellgallery.com/api/v2/


PS> Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
PS> Get-PSRepository

Name                      PackageManagementPro InstallationPolicy   SourceLocation
                          vider
----                      -------------------- ------------------   --------------
PSGallery                 NuGet                Trusted              https://www.powershellgallery.com/api/v2/

When that was completed, what I needed was to download all the xJEA modules from the PowerShell Gallery, at once, and place them into a folder for each version. Luckily, the Save-Module cmdlet will handle the file structure. This means, each module (a DSC resource, in this case) will be saved inside a version folder, nested in the same xJea folder. I gave the cmdlet a base path (C:\), and it did the rest. Take a look below at the example and image.

PS> Find-Module -Name xJea -AllVersions |
>> ForEach-Object {Save-Module -Path C:\ -Name xJea -RequiredVersion "$($_.Version.ToString())"}

Search PowerShell Gallery Module for requires01

Once I had all the files downloaded, I needed to run through all the contained .psm1 files for the requires statement for PowerShell version 5.0. The command below returned 51 files where it located the string “requires -version 5.”

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path
C:\xJea\0.2.10\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.10\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.10\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.11\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.11\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.11\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.12\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.12\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.12\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.13\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.13\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.13\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.14\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.14\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.14\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.15\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.15\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.15\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.1\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.1\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.1\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.2\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.2\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.2\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.3\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.3\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.3\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.4\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.4\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.4\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.5\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.5\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.5\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.6\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.6\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.6\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.5\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.5\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.6\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.6\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.7\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.7\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.8\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.8\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.8\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.9\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.9\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.9\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1

In the next two examples, I haven’t included all the results to save some space. The example below, takes the previous one, and removes the portion of the path to the left of the version number. Compare the last line in the example above with the last line in the example below, and you’ll see how the .Split() method cleaned up the beginning of each result.

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path |
>> ForEach-Object {$_.Split('\',3)[-1]}
0.2.10\DSCResources\Library\JeaAccount.psm1
0.2.10\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
0.2.10\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
0.2.11\DSCResources\Library\JeaAccount.psm1
0.2.11\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
0.2.11\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
...
0.2.9\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1

In this example, we added a second .Split() method to clean up the path information to the right of the version number. Now, we only have the versions, although we do have duplicates.

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path |
>> Foreach-Object {$_.Split('\',3)[-1].Split('\')[0]}
0.2.10
0.2.10
0.2.10
0.2.11
0.2.11
0.2.11
..
0.2.9

This example below, shows the full results again; however, each version is only listed one time. This is because we’ve piped the previous results to Select-Object -Unique. This means, for example, we only see 0.2.11 once, even though it appeared three times in the last example.

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path |
>> ForEach-Object {$_.Split('\',3)[-1].Split('\')[0]} |
>> Select-Object -Unique
0.2.10
0.2.11
0.2.12
0.2.13
0.2.14
0.2.15
0.2.16
0.2.16.1
0.2.16.2
0.2.16.3
0.2.16.4
0.2.16.5
0.2.16.6
0.2.5
0.2.6
0.2.7
0.2.8
0.2.9

We now have a complete list of all the versions that require PowerShell 5.0. That means we can determine which versions will run with PowerShell 4.0.

In this final example, we set a variable, $VersionsRequireWMF5, to the results produced by our last command. These are the versions that require PowerShell 5.0. In the middle of the example below, we set a second variable, $AllVersions, to all the versions of xJEA that we downloaded. We did this by running a Get-ChildItem (think, dir or ls) against the C:\xJea directory and returned just the directory names. That gives us two variables that we can supply to the Compare-Object cmdlet. That cmdlet will tell us the differences between the values of the variables, indicating which versions I can use with WMF 4.0.

PS> $VersionsRequireWMF5 = (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' | 
>> Get-Content) -match 'requires -version 5' | 
>> Select-Object PSPath | Convert-Path | 
>> ForEach-Object {$_.Split('\',3)[-1].Split('\')[0]} | 
>> Select-Object -Unique
PS>  
PS> $AllVersions = (Get-ChildItem -Path C:\xJea | Select-Object).Name
PS>  
PS> Compare-Object -ReferenceObject $AllVersions -DifferenceObject $VersionsRequireWMF5

InputObject SideIndicator
----------- -------------
0.2         <=
0.2.1       <=
0.2.2       <=
0.2.4       <=

Based on these results, version 0.2, 0.2.1, 0.2.2, and 0.2.4, do not require WMF 5.0 and can be tested with WMF 4.0, before moving to the newer version of WMF. You can expect that I’ll work with 0.2.4 tomorrow morning.

A final note: Microsoft may not yet think they need it, but they should indicate the version of PowerShell that is required by modules on the PowerShell Gallery. That needs to be a part of their approval process, and the results need to added to the web interface, and made a part of the results returned by the Find-Module cmdlet (see the full Find-Module results below). These results should include a Requires property. This isn’t going to be the last time someone comes up against this problem, especially as we move into versions post WMF 5.0.

PS> Find-Module -Name xJea | Select-Object *

Name : xJea
Version : 0.2.16.6
Description : Module with DSC Resources for Just Enough Admin (JEA). Jea makes it simple to create
custom RBAC solutions using PowerShell.
Author : Microsoft Corporation
CompanyName :
Copyright : (c) 2014 Microsoft Corporation. All rights reserved.
PublishedDate : 5/14/2015 7:51:23 PM
LicenseUri :
ProjectUri :
IconUri :
Tags : {PSModule, PSIncludes_DscResource}
Includes : {Function, DscResource, Cmdlet, Command}
PowerShellGetFormatVersion :
ReleaseNotes :
Dependencies : {}
RepositorySourceLocation : https://www.powershellgallery.com/api/v2/
Repository : PSGallery
PackageManagementProvider : NuGet

If you think this should be included, then up vote the feedback I’ve left on connect.microsoft.com.

Only Return System.DateTime Properties from Get-ADComputer

I had one of those randomly appearing PowerShell questions last night. Windows PowerShell is a huge interest for me, so there’s really no surprise.

I wondered, How can only return the date time related properties from Get-ADComputer? It seems to happen quite often that I’ll need to view date and time information from Get-ADComputer (and Get-ADUser). It’s mildly cumbersome to scan though all the properties looking for dates and times in the property’s value — the ultimate reason behind this random thought.

The command I threw together was ugly, and seemed to lack a simpler approach. I stopped there, and decided to pass this one out to the community. Can you come with a better way?

Again, the idea is to return only the properties from Get-ADComputer that are System.DateTime properties (have System.DateTime in the Definition property of Get-Member). Take a look at my example and you might better understand my goal. I didn’t bother filtering out the default properties returned by Get-ADComputer (at first), but you’re welcome to do that, too. Cheers!

PS> Get-ADComputer -Identity SERVER01 -Properties ((Get-ADComputer -Identity SERVER01 -Properties * | Get-Member | Where-Object Definition -match 'System.DateTime').Name)

AccountExpirationDate  :
AccountLockoutTime     :
Created                : 9/12/2013 1:34:06 PM
createTimeStamp        : 9/12/2013 1:34:06 PM
DistinguishedName      : CN=SERVER01,OU=Finance,DC=mydomain,DC=com
DNSHostName            : SERVER01.mydomain.com
Enabled                : True
LastBadPasswordAttempt :
LastLogonDate          : 3/17/2014 10:35:35 AM
Modified               : 8/18/2014 11:48:34 AM
modifyTimeStamp        : 8/18/2014 11:48:34 AM
Name                   : SERVER01
ObjectClass            : computer
ObjectGUID             : 234cbaed59-1ab3-6ebc-9782-e9542bedaec
PasswordLastSet        : 3/14/2014 5:12:24 PM
SamAccountName         : SERVER01$
SID                    : S-1-5-21-174985627-956854884-123956358-942569
UserPrincipalName      :
whenChanged            : 8/18/2014 11:48:34 AM
whenCreated            : 9/12/2013 1:34:06 PM

Update1: I went ahead and edited the command so that it would not return the default Get-ADComputer properties (Name, SamAccountName, etc.), using the Select-Object cmdlet. I repeated the command I issued to the -Properties parameter of Get-ADComputer, as the value for Select-Object.

So, any takers? Can you come up with a better way to do this?

PS> Get-ADComputer -Identity SERVER01 -Properties ((Get-ADComputer -Identity SERVER01 -Properties * | Get-Member | Where-Object Definition -match 'System.DateTime').Name) | Select-Object (Get-ADComputer -Identity SERVER01 -Properties * | Get-Member | Where-Object Definition -match 'System.DateTime').Name

AccountExpirationDate  :
AccountLockoutTime     :
Created                : 9/12/2013 1:34:06 PM
createTimeStamp        : 9/12/2013 1:34:06 PM
LastBadPasswordAttempt :
LastLogonDate          : 3/17/2014 10:35:35 AM
Modified               : 8/18/2014 11:48:34 AM
modifyTimeStamp        : 8/18/2014 11:48:34 AM
PasswordLastSet        : 3/14/2014 5:12:24 PM
whenChanged            : 8/18/2014 11:48:34 AM
whenCreated            : 9/12/2013 1:34:06 PM

Update2: There’s a second update to this post. Jonathan Angliss tweeted a much cleaner solution — exactly what I had hoped someone might do. Here’s his contribution:

PS> Get-ADComputer -Identity SERVER01 -Properties * | ForEach-Object {$_.psobject.properties | Where-Object {$_.TypeNameofValue -eq 'System.DateTime'}} | Select-Object Name,Value

Invoke a Command from a Get-History Menu

Sometimes you line up the right cmdlets, appreciate what you’ve done for a moment, only to become mildly irritated that you never thought of that before. I just did that.

We all know, or should rather, that the Get-History cmdlet returns a list of commands that have been entered during the current Windows PowerShell session. If you open up a new console, enter a couple commands, and then run Get-History, or one of its aliases (ghy, h, history), it’ll show you what commands you’ve entered up until that point. Take this example, for instance:

PS> Get-Process | Select-Object -Last 2

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    263      11     1680       5132    46     0.02   3356 WUDFHost
    227      12     1824       6804    47     0.22   3840 WUDFHost


PS> Get-Service | Select-Object -First 2

Status   Name               DisplayName
------   ----               -----------
Running  AdobeARMservice    Adobe Acrobat Update Service
Stopped  AdobeFlashPlaye... Adobe Flash Player Update Service


PS> Get-History

  Id CommandLine
  -- -----------
   1 Get-Process | Select-Object -Last 2
   2 Get-Service | Select-Object -First 2


PS> Get-History

  Id CommandLine
  -- -----------
   1 Get-Process | Select-Object -Last 2
   2 Get-Service | Select-Object -First 2
   3 Get-History

I use the up arrow quite often to cycle through my previous commands, and press Enter when I’ve found the one I want to run again. That’s one way to rerun a previously run command. Another, is to use the Get-History cmdlet. First, you use it to determine the ID of the command you want to rerun, such as we’ve done above. Then, you can run Get-History again with the -Id parameter and pipe that result to Invoke-History (aliases: ihy, r), such as we’ve done below. You can also just use Invoke-History -Id 1 without the use of Get-History.

PS> Get-History -Id 1 | Invoke-History
Get-Process | Select-Object -Last 2

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    263      11     1680       5132    46     0.02   3356 WUDFHost
    227      12     1824       6804    47     0.22   3840 WUDFHost

What about making a “menu,” where we can select the command we want to run and press OK to run it? Easy. If you’ve been paying attention to PowerShell, then you’ve probably seen the Out-GridView cmdlet before. Let’s put it to good use, and I’ll show you the cmdlets I lined up.

PS> Get-History | Out-GridView -PassThru | Invoke-History

When I enter the command above, it will show the dialog box below; however, your dialog box will be the default size. I’ve resized mine so that the image better fits on this page. If it’s important to you, you can change what the title says by including Out-GridView’s -Title parameter: Get-History | Out-GridView -PassThru -Title ‘History Menu’ | Invoke-History.

Invoke-a-Command-from-Get-History-Menu01

After I select an option from the list, and press OK, the history item will be rerun.

PS> Get-History | Out-GridView -PassThru | Invoke-History
Get-Process | Select-Object -Last 2

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    263      11     1680       5132    46     0.02   3356 WUDFHost
    227      12     1824       6804    47     0.22   3840 WUDFHost

PS>

Well, there it is. Something so simple and so obvious that I can’t believe I never thought of it before.

Get Active Directory (Sites and Services) Subnets

I just started reading chapter 16 — Managing sites and subnets — in Richard Siddaway‘s book Learn Active Directory in a Month of Lunches. I already know a good deal about Active Directory (AD). I’ve been using and supporting it, in one fashion or another, since its introduction in 2000. Even so, that didn’t stop me from picking up this title. I’ll buy and read a book about a topic I already know, even if I’m only going to pick up a few new details. I also like to remind myself of things I already know, in an effort to keep things as fresh as possible in my mind. I’ve also found myself curious about other people’s writing styles. Since I’ve been writing and posting here, for coming up on a year and a half, I believe that my writing has improved, and it makes me eager to read other people’s writings, too.

I went a little off topic there, but anyway, I read a couple pages of the chapter, and then typed the command below into my Windows PowerShell console. This was in an effort to determine the subnets used by an AD site, before reading any further.

PS> (Get-ADReplicationSite -Identity Downtown02 -Properties Subnets).Subnets.ForEach{$_.Split(',',2)[0].Split('=')[-1]}
10.115.0.0/16
10.122.0.0/15
10.140.0.0/16

So what’s happening here? By default, the Active Directory Get-ADReplicationSite cmdlet returns information about your (computer’s) AD site; however, you can filter the results to get information about another AD site, or even, all the AD sites. In my example, I’ve indicated that I only want information about a single site, Downtown02. Although you don’t see it in the default properties that are returned by this cmdlet, there are extended properties returned when the -Properties parameter is included. This behavior is similar to some of the other AD cmdlets. In fact, you can see which AD cmdlets have a -Properties parameter by enter this: Get-Command -Module ActiveDirectory -ParameterName Properties. In our example, we are only returning the Subnets property, not all of the properties, as we would when using -Properties *.

The Subnet property is returned as a Distinguished Name, such as: CN=10.115.0.0/16,CN=Subnets,CN=Sites,CN=Configuration,DC=mydomain,DC=com. Due to this, I’ve added the .Split() method, twice. The first time it was used, we split at the comma and returned two pieces of our string, when it’s entered as .Split(‘,’,2). That’s difficult to explain, so here’s what that looks like:

PS> (Get-ADReplicationSite -Identity Downtown02 -Properties Subnets).Subnets.ForEach{$_.Split(',',2)}
CN=10.115.0.0/16
CN=Subnets,CN=Sites,CN=Configuration,DC=catnet,DC=mydomain,DC=com
CN=10.122.0.0/15
CN=Subnets,CN=Sites,CN=Configuration,DC=catnet,DC=mydomain,DC=com
CN=10.140.0.0/16
CN=Subnets,CN=Sites,CN=Configuration,DC=catnet,DC=mydomain,DC=com

In the example above, notice it only split at the first comma in the string. That’s what the 2 does (“leave me 2 sections”). Adding the [0] index, below, only keeps the first element of the results of each split.

PS> (Get-ADReplicationSite -Identity Downtown02 -Properties Subnets).Subnets.ForEach{$_.Split(',',2)[0]}
CN=10.115.0.0/16
CN=10.122.0.0/15
CN=10.140.0.0/16

The second time we use the .Split() method, we split at the equal sign and return the last element, like so: .Split(‘=’)[-1]. After these two .Split() methods have run, we have our subnets, just like we’d see them in the Name category, of the Active Directory Sites and Services MMC. I should mention the syntax in this command. Take a closer look, if you haven’t already: It’s using the ForEach method, not the ForEach-Object cmdlet, as you might have expected to see. This syntax became possible in PowerShell 4.0.

Okay, so I’ve mentioned this a time or two, now. I often learn something new, share it here, and then I find a better way. Sometime, while I’m still writing. Well, that happened again. We don’t need to get fancy with the results of Get-ADReplicationSite as we have above, and can instead, use the Get-ADReplicationSubnet cmdlet. How’d I find the cmdlet? Well, (while writing), I decided to search for the string “subnet” in all the cmdlet names from the ActiveDirectory module: Get-Command -Module ActiveDirectory -Name *subnet*. Here’s the command to use to return the same information we did with the first example in this post.

PS> (Get-ADReplicationSubnet -Filter * -Properties Site | Where-Object Site -like '*Downtown02*').Name
10.115.0.0/16
10.122.0.0/15
10.140.0.0/16

If you’re wondering why I didn’t use the -Filter parameter to filter on the site, well then, here you go. Here’s a quicker and more efficient method in which to write the command.

PS> (Get-ADReplicationSubnet -Filter {Site -eq 'Downtown02'}).Name
10.115.0.0/16
10.122.0.0/15
10.140.0.0/16

Thanks for joining me, and now back to my reading.