Require Use of a Cmdlet’s Prefix

I learned something new today. That’s always fun. Okay, not always, but when it’s about PowerShell, it often is. When you import a module — we’ll use the Active Directory module as an example — and use a prefix, you can still use the original cmdlet names. We’ll begin by importing the Active Directory PowerShell module to include a prefix.

PS > Import-Module -Name ActiveDirectory -Prefix Lol

With that module now imported, we’ll execute a Get-Command, command. As the Name parameter allows for wildcards, we’ll instruct it to return all the commands PowerShell knows about, at this stage in the session, that include the ADUser suffix from the ActiveDirectory module.

PS > Get-Command -Name '*ADUser' -Module ActiveDirectory

CommandType     Name                      Version    Source
-----------     ----                      -------    ------
Cmdlet          Get-LolADUser             1.0.0.0    ActiveDirectory
Cmdlet          New-LolADUser             1.0.0.0    ActiveDirectory
Cmdlet          Remove-LolADUser          1.0.0.0    ActiveDirectory
Cmdlet          Set-LolADUser             1.0.0.0    ActiveDirectory

The above command results indicates that PowerShell knows about four commands that end with the ADUser suffix. Each of those commands that it knows about, include the Lol prefix we used in the first command we issued, Import-Module. So far, everything is going as expected. Or is it?

Check this out. When we opt to not use the Module parameter and use Where-Object and the Source property instead, we have some different results. This is just getting weird, but at minimum, there’s a bit of proof here that the original cmdlets still exist, as well as the prefixed versions.

PS > Get-Command -Name '*ADUser' | Where-Object -Property Source -eq ActiveDirectory

CommandType     Name                      Version    Source
-----------     ----                      -------    ------
Cmdlet          Get-ADUser                1.0.0.0    ActiveDirectory
Cmdlet          Get-LolADUser             1.0.0.0    ActiveDirectory
Cmdlet          New-ADUser                1.0.0.0    ActiveDirectory
Cmdlet          New-LolADUser             1.0.0.0    ActiveDirectory
Cmdlet          Remove-ADUser             1.0.0.0    ActiveDirectory
Cmdlet          Remove-LolADUser          1.0.0.0    ActiveDirectory
Cmdlet          Set-ADUser                1.0.0.0    ActiveDirectory
Cmdlet          Set-LolADUser             1.0.0.0    ActiveDirectory

If we reissue the Get-Command command and indicate we only want the Get-ADUser command specifically, it shows up, too. Still, it’s odd that it wasn’t included in the first results. I think most of us would have expected to see it there, too. We’ll get back to this in a moment.

PS > Get-Command -Name Get-ADUser -Module ActiveDirectory

CommandType     Name                      Version    Source
-----------     ----                      -------    ------
Cmdlet          Get-ADUser                1.0.0.0    ActiveDirectory

About this time in my poking around, is when I started examining the imported module. The first thing I noted, due to the default output, was that the first two commands I could see, under ExportedCommands, didn’t include the noun prefix. It’s more proof the original cmdlets are still around.

PS > Get-Module -Name ActiveDirectory

ModuleType Version Name            ExportedCommands
---------- ------- ----            ----------------
Manifest   1.0.0.0 ActiveDirectory {Add-ADCentralAccessPolicyMember, Add-ADComputerServiceAcc...

The next command I ran was to better view these exported commands. I haven’t included all the results, but I’m sure you’ll get the idea.

PS > (Get-Module -Name ActiveDirectory).ExportedCommands

Key                                               Value
---                                               -----
Add-ADCentralAccessPolicyMember                   Add-LolADCentralAccessPolicyMember
Add-ADComputerServiceAccount                      Add-LolADComputerServiceAccount
Add-ADDomainControllerPasswordReplicationPolicy   Add-LolADDomainControllerPasswordReplicationPolicy
Add-ADFineGrainedPasswordPolicySubject            Add-LolADFineGrainedPasswordPolicySubject
Add-ADGroupMember                                 Add-LolADGroupMember
Add-ADPrincipalGroupMembership                    Add-LolADPrincipalGroupMembership
Add-ADResourcePropertyListMember                  Add-LolADResourcePropertyListMember
Clear-ADAccountExpiration                         Clear-LolADAccountExpiration
Clear-ADClaimTransformLink                        Clear-LolADClaimTransformLink
Disable-ADAccount                                 Disable-LolADAccount
...

You’ll notice that we’re dealing with a hash table. For every key named with the original cmdlet name, we have a value that is the cmdlet name with the noun prefix. The hash table appears that it may be used to know the relationship between the original cmdlets and its matching prefixed version. For fun, I opened a new PowerShell session and imported the Active Directory module without the prefix. In that case, the above hash table had the same command for a Key and its matching Value.

This finding made me think of something. Perhaps that Get-Command, command that included the Module parameter, only checked for commands against the values in this hash table, but the Where-Object cmdlet, returned the keys and values from the hash table. Just a thought; I said I get back to this a moment ago.

So here’s why I really decided to write. What if I don’t want to allow a user to use the original command. I don’t want them to use Get-ADUser to invoke Get-ADUser; instead, I want them to use the prefixed version only. Why, I don’t exactly know, but here’s how I fixed that one.

We’ll pretend that we’ve yet to import the Active Directory module; we’re starting fresh with the below sequence of commands. We’ll begin by creating two variables: $Module and $Prefix. In the subsequent command, we’ll import the Active Directory module providing it’s yet to be imported. When it’s imported, we’ll include the Prefix parameter with the Lol value. This means, Get-ADUser becomes Get-LolADUser, as can be figured out from the previous examples.

PS > $Module = 'ActiveDirectory'; $Prefix = 'Lol'

PS > If (-Not(Get-Module -Name $Module)) {
>>> Import-Module -Name $Module -Prefix $Prefix
>>> }

PS > $ExportedCommands = (Get-Module -Name $Module).ExportedCommands

PS > Foreach ($Command in $ExportedCommands.GetEnumerator()) {
>>> New-Item -Path "Function:\$($Command.Key)" -Value "Write-Warning -Message 'Please use the $($Command.Key.Replace('-',"-$Prefix")) function.'" -Force
>>> }

After that, we’ll get all of the ExportedCommand into a variable with the same name. Then, the Foreach command iterates over the keys in our hash table — the original cmdlet names — and creates a function for each one of them. One minute, Get-ADUser executes as expected, and the next minute, it doesn’t. Now, every time someone enters the actual AD cmdlet name, a function with the same name will execute instead. It will indicate to the user to use the prefixed version. Here’s a few examples.

PS > Get-ADUser -Identity tommymaynard
WARNING: Please use the Get-LolADUser function.

PS> Unlock-ADAccount tommymaynard
WARNING: Please use the Unlock-LolADAccount function.

PS > Get-ADDomain
WARNING: Please use the Get-LolADDomain function.

PS > (Get-LolADUser tommymaynard).SamAccountName
tommymaynard

No idea if this might ever prove useful, but at minimum, I now know I can change the default behavior of a cmdlet, in order to point someone toward the prefixed version. Oh, and the reason why the function I created is run instead of the cmdlet, is because of command precedence. Read about it; it’s beneficial to know. I knew it existed and I’ve used it, I’ve just never used it in bulk to create functions to run in place of so many cmdlets. Additionally, however, I’m not sure if I’ve ever created a function by using the New-Item cmdlet directly against the Function PSDrive. Neat.

Enjoy the weekend.

Update: Ah, it’s Monday again. This seems to happen at least once a week. There’s something I wanted to note, and it’s not just the day. Even though we’re using a function to indicate to users to use the prefixed cmdlet, the original cmdlet can still be used. If someone uses the full path to the cmdlet, the function will be ignored. That’s to say, this still works.

PS > ActiveDirectory\Get-ADUser

Leave a Reply

Your email address will not be published. Required fields are marked *