Tag Archives: Import-Module

PowerShell and the LAPS Windows PowerShell Module

There’s a difference between Windows PowerShell and just PowerShell. You know that, right? You know what the difference is, yeah? If not, then do know that Jeff Hicks wrote a great blog post about it recently titled “What the Shell is Happening?” I’m going to assume you’ve read this as I go along, so you might just do that quickly if you haven’t already.

Windows PowerShell and PowerShell really are two different things. We’re moving into a territory where you’ll be able to know how much someone knows about Windows PowerShell/PowerShell based on how they are discussed. It’s going to be like PowerShell quoting rules: You can tell a good deal about someone’s Windows PowerShell/PowerShell knowledge based on when they do and don’t use single and double-quotes.

I was recently looking at some documentation when I happened upon the LAPS Windows PowerShell module. If you’re looking for a strangely named module then look no further; it’s AdmPwd.PS. Seriously. How about just calling it LAPS!? Up until that point in the documentation review I was doing, everything I tried was working in PowerShell — commands from the Hyper-V module, commands from the ActiveDirectory module (read the Edit section at the bottom of this post when done reading this post), and all of the we-ship-these-with-the-product Microsoft modules and commands.

The LAPS module — we’ll just call it that — is stored not only as a Windows PowerShell module, but it’s stored in the C:\Windows folder. That’s C:\Windows\System32\WindowsPowerShell\v1.0\Modules to be exact. While tab-completion didn’t work on the module’s name, the Import-Module cmdlet did work, but not without a warning.

The warning message above has to do with how PSRemoting sessions work. First off, a PSRemoting session is usually done from one computer to another. In this instance, however, it’s doing a PSRemoting session from and to the same computer: mine. In that remoting session, it’s loading up Windows PowerShell (powershell.exe [not pwsh.exe]). As can be seen below, running Get-PSSession will provide information about the Windows PowerShell (or WinPSCompatSession) PSRemoting session. Notice the -1 value for the IdleTimeout. Without any reading and research, I’m going to say that it means the session doesn’t timeout. I left mine up for an hour or so, and sure enough, the LAPS commands continued to work without having to recreate a PSRemoting session.

In any PSRemoting session, whether to the same computer or another, we get deserialized objects as the results. That means that the final results, that end up on the source computer from the destination computer, do not consist of live PowerShell objects. I’m sure you can read more about it, but the result/output is serialized into XML, sent across the wire from one computer to the other, and then deserialized. This has to do with how to quickly move data from one machine to another. In our case, even though it’s the same computer, it’s still making use of the network card and serialization/deserialization. It’s still doing this even if your PSRemoting session is to and from the same computer.

But there’s another way we can use this module. As mentioned in our warning message, Import-Module includes a SkipEditionCheck switch parameter. According to the documentation, this forces the command to skip evaluating CompatiblePSEditions in a module’s manifest file. This is a key within the file that indicates whether or not a module can be used for Windows PowerShell, which uses the term “Desktop” if it can, or PowerShell, which uses the term “Core” if it can. If a module was designed to work with both Windows PowerShell and PowerShell, it would include both terms. The LAPS module was written before CompatiblePSEditions was added to the module manifest files (.psd1 files).

When this parameter is used, it doesn’t look for CompatiblePSEditions, as stated, and appears to load the LAPS module properly. Well, properly enough that I was able to test the Get-AdmPwdPassword command. Before we see some proof supporting that claim, let’s take a look at some very important information from the Import-Module documentation.

It turns out it’s very likely this module and its related commands wouldn’t have worked using the SkipEditionCheck parameter, but so far they do. Oh, I didn’t mention this, but in the documentation for Import-Module and the SkipEditionCheck parameter, it does mention the path where the LAPS module is located, “Allows loading a module from the “$($env:windir)\System32\WindowsPowerShell\v1.0\Modules” module directory into PowerShell Core when that module does not specify Core in the CompatiblePSEditions manifest field.” So, a part of how this works is also due to the module’s location in the file system. Now, here’s our successful invocation of the Get-AdmPwdPassword command.

By using this method — and we’re lucky we can it seems — we skip creating a PSRemoting session from/to the same computer. Therefore, we aren’t forced to work with deserialized objects. Typically, it’s fine if you do, but with the LAPS module and PowerShell, it’s not a requirement. Take a look at the TypeName value above. That’s a clear indication that we are working with live PowerShell objects. We didn’t see this earlier in our PSRemoting session, but if we had, it would’ve said, “Deserialized.AdmPwd.PSTypes.PasswordInfo.”

Edit: A few days have passed since I first published this post. I didn’t know it then, but I do now! The ActiveDirectory module in PowerShell also creates a PSRemoting session! It looks like it’s running in PowerShell, but it’s really running in Windows PowerShell “behind the scenes.” I’ve had no problems dealing with a deserialized objects, by the way. This is because any piping I might do happens on the remote computer (my computer, but yeah), before the serialization/deserialization process.

Change Prompt on Module Import

To me, my PowerShell prompt is quite important. I’ve written about it nine times already. While I’m not going to write about it again, so much, I am going to focus on an updated prompt I’ve created for a recent project. I couldn’t help but take a few things into this project from my prompt, and that’s why that’s been mentioned.

I recently received a screen capture from someone running into a problem using one of the tools I’ve written. Sure, it needs some error checking, I won’t deny that, but it was a very obscure and unforeseen problem. You know, how we often find out about error conditions.

This tool, or function, can be run on an Amazon Web Services EC2 instance within a project, to determine the status of its partner EC2 instance. When it works, it returns the status of the other instance, to include things like running, stopping, stopped, etc. There’s also a couple other companion functions that can start and stop the partner instance. The second of the two machines has very high specifications, and so we ask that our users shut down those secondary instances when they’re not running experiments. They pricey.

The problem is that when I look at this error, the prompt doesn’t tell me enough. I can only tell it’s PowerShell — the PS in the prompt — and the current path — some of which has been hidden in this first image. I want more information without having to ask the user, and so I’ve added that in.  Here’s the default prompt up close.

The new prompt includes the username (to the left of the @), the computer name (to the right of the @), the project name (while it’s not in this example, it’s normally a part of the computer name), the path, and whether the user is an admin (#) or not ($). Now, when I receive a PowerShell screen capture, I already have a few of my first questions answered.

All of the functions, such as the one that was run that generated this error, are a part of the same PowerShell module. There’s somewhere near 20 of them so far, and I keep finding reasons to add new ones. If you don’t know, creating a PowerShell script module that includes a module manifest file (a .psd1), allows one to include scripts that execute just before a module is imported. This, whether the module is imported manually using Import-Module, or by invoking a function from within the module, when the module hasn’t yet been imported.

Let’s take a look at my SetPrompt.ps1 script file that executes when my PowerShell module is imported.

Function prompt {
    # Determine Admin; set Symbol variable.
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'} Else {$Symbol = '$'
    }

    # Create prompt.
    "[$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $($executionContext.SessionState.Path.CurrentLocation)]$Symbol "
} # End Function: prompt.

With this script in place and a ScriptsToProcess entry in the module’s manifest file, pointing to this file, I can be ensured that the user’s prompt will change the moment my module is imported. From here on out, I can rest assured that if a user — of these machines at least — sends us a screen capture that it’ll include relevant pieces of information I would have had to ask for, had this prompt not been in place.

There’s a final thought here. When the user is done with this PowerShell module, they’re still going to have this prompt. In my case, it’s perfectly suitable because my users won’t be in PowerShell, unless they’re issuing commands from the module. At least, I can’t imagine they will be. The only other thoughts I’ve had about this “problem” would be to (1) teach users to remove the module, and have code in the prompt monitor whether the module is loaded or not, and revert the prompt if the module is removed, or (2) revert the prompt if a command outside of my module is invoked.

That’s it for today. I don’t have it shown here, but it’s neat to see the prompt alter itself when the module is loaded. Something to keep in mind, if you find yourself in a similar situation.

Update: I was annoyed that $HOME, or C:\Users\tommymaynard, was being displayed as the full path, so I made some additional modifications to the prompt that’s being used for this project. It’ll now look like this, when at C:\Users\tommymaynard (or another user’s home directory).

Here’s the new prompt function, to include a better layout.

Function prompt {
    # Determine Admin; set Symbol variable.
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'
    } Else {
        $Symbol = '$'
    }

    If ((Get-Location).Path -eq $env:USERPROFILE) {
        $Path = '~'
    } Else {
        $Path = (Get-Location).Path
    }

    # Create prompt.
    "[$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Path]$Symbol "
} # End Function: prompt.

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

Script Sharing – Quickly Remove and Import a Module (Reset-Module)

Often in Windows PowerShell module development, you’ll need to remove and import a module again, and again, to get the newest changes to your functions. It wasn’t long after some recent coding, that I tired from entering rmo mymodule and ipmo mymodule (the rmo alias is for Remove-Module and ipmo is used for Import-Module). Even when I added both commands to the same line, with a semi-colon in between: rmo mymodule; ipmo mymodule, and used wildcards: rmo mym*;ipmo mym*, I was still annoyed it was taking too much time.

Enter this quick function and alias:

Set-Alias -Name dump -Value Reset-Module
Function Reset-Module {
    [CmdletBinding()]
    Param ()

    Begin {
    } # End Begin.

    Process {
        Write-Verbose -Message 'Removing module.'
        Remove-Module -Name mymodule -ErrorAction SilentlyContinue -Verbose:$false
        Write-Verbose -Message 'Adding module.'
        Import-Module -Name mymodule -Verbose:$false
    } # End Process.

    End {
    } # End End.
} # End Function.

Now, I simply enter dump and it will remove and import the module again, with the newest changes. It’s PowerShell: You can use it to develop, and use it to speed up development.

Note: In case you’re thinking it, yes, there are many things that can be improved in this function: checking for the module before trying to remove it (and eliminating the -ErrorAction parameter in Remove-Module), not hard-coding the module’s name and using a parameter instead, and adding help. Even so, I wanted to throw something together quickly that probably wouldn’t live long after the end of the module’s development. Providing it does, I can add some refinements later.

Working with an Array of Arrays

There’s a group of servers that I use for a specific project. I can never remember their names or how those correspond to their roles (web front end vs. data back end). Although I’ve updated their Active Directory (AD) descriptions, and created two, specifically named AD groups for them, I wanted an even quicker way to remind myself whose who. With that in mind, I updated my profile with a custom variable that is an array, of arrays.

In this Quick Learn, we’ll work with an example that actually uses three groups of roles — our DCs, our web servers, and our SQL servers. As you’ll soon see, our servers are named after Santa Claus’ reindeer. These names have nothing to do with the role of these servers, and since their names are all closely related, it’s difficult to remember who does what.

This first example, below, demonstrates how we create a new variable, or modify an already existing variable. When we echo the contents of our variable, we get all the computer names, regardless of what array they are in, within the base array. The term ‘base array’ is probably not something you’ll hear or read about outside this post. It’s being used here to help distinguish the array that holds all the other arrays — the container array.

PS C:\> Set-Variable -Name Computers -Value @(('dasher','vixen','cupid'),('comet','dancer','donner'),('blitzen','rudolph','prancer'))
PS C:\> $Computers
dasher
vixen
cupid
comet
dancer
donner
blitzen
rudolph
prancer

We can use an index to return one of the arrays within the base array. In the examples below, you can see how each can be accessed. This is probably a good time to review indexes: The first item in an array is index zero, the second item is index one, the third item is index two, and so on.

PS C:\> $Computers[0]
dasher
vixen
cupid
PS C:\> $Computers[1]
comet
dancer
donner
PS C:\> $Computers[2]
blitzen
rudolph
prancer

In the following example, we can use two indexes to access a specific server. The first index represents which array (within the base array) I want to return, like it did above, and the second index indicates which server I want to return.

PS C:\> $Computers[0][2]
cupid
PS C:\> $Computers[1][0]
comet
PS C:\> $Computers[2][1]
rudolph

The difficult part is going to be able to remember which index is for the DCs, the web servers, or the SQL servers. In that case, I’ll create three more variables to use in place of those index integers.

PS C:\> Set-Variable -Name DCs -Value 0
PS C:\> Set-Variable -Name Web -Value 1
PS C:\> Set-Variable -Name SQL -Value 2
PS C:\> $DCs,$Web,$SQL
0
1
2

With the combination of my $Computers variable and the three, role-specific variables, I am able to easily return the set of servers I want.

PS C:\> $Computers[$DCs]
dasher
vixen
cupid
PS C:\> $Computers[$Web]
comet
dancer
donner
PS C:\> $Computers[$SQL]
blitzen
rudolph
prancer

Now that we have this all figured out, I can use them in different commands. Here’s a couple examples:

PS C:\> $Computers[$SQL] | ForEach-Object {Test-Connection $_ -Count 1}

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)
------        -----------     -----------      -----------                              -----    --------
TOMMYMS PC... blitzen         10.10.10.80                                               32       1
TOMMYMS PC... rudolph         10.10.10.81                                               32       1
TOMMYMS PC... prancer         10.10.10.82                                               32       1

PS C:\> Get-Service -ComputerName ($Computers[$DCs]) -Name *bit* | Select-Object MachineName,Name,Status | Format-Table -AutoSize

MachineName Name  Status
----------- ----  ------
dasher      BITS Stopped
vixen       BITS Running
cupid       BITS Stopped

The unfortunate thing about hard coding computer names in our profile, is that we’ll run into problems when new servers are added and old ones are decommissioned. Therefore, we’re going to use AD groups — something I mentioned earlier — to populate our array variable. We’ll pull our DCs from the Domain Controllers, and our web servers and SQL servers from two fictions AD groups: WebServers and SQLBoxes. Here’s the command we’ll add to our profile to ensure we always have the correct server names. While this can all be on a single line command I’ve added line breaks to give it an easier-to-read appearance.

Set-Variable -Name Computers -Value @(
    ((Get-ADDomainController -Filter *).Name),
    ((Get-ADGroupMember -Identity WebServers).Name),
    ((Get-ADGroupMember -Identity SQLBoxes).Name)
)

And, that’s it. If for some reason you’re using PowerShell 2.0 or lower (and I really hope you’re not), you’ll need to include Import-Module -Name ActiveDirectory in your profile. As well — and it should go without saying — you’ll need to be working from a computer that actually has the ActiveDirectory module installed. Although I’m using PowerShell 4.0, I still include Import-Module -Name ActiveDirectory in my profile, so I don’t have to wait for it to auto load, when I run my first AD cmdlet.

Cmdlets of the Same Name (VMware & Hyper-V)

Update: This post was submitted to PowerShell.org for their PowerShell TechLetter. For the most update to date version of this post, please read it here: http://powershell.org/techletter/issues/2015-01-january.php#Article1

One of the first things I did when I moved to Windows 8.1 (and Windows PowerShell 4.0, by default), was to add the Hyper-V feature. If you didn’t already know, yes, you can run virtual machines in Windows 8.1 without the need for third-party software. I can’t be 100% certain, but I’m fairly certain I installed this feature a year ago on Windows 8, as well. In both operating systems, you will need to meet some hardware requirements to install Hyper-V, otherwise the feature will be grayed out. In addition, it may not even be listed in non-Pro and non-Enterprise versions of Windows 8.1.

Adding Hyper-V was twofold. One, I wanted to gain experience with the Hyper-V cmdlets and two, I wanted to add at least a couple virtual machines to my computer – Windows 8.1 to test PowerShell 5.0, and Windows 7 with PowerShell 3.0. After a day or so of playing with Hyper-V, it occurred to me that my future work with PowerCLI, VMware’s vSphere PowerShell PSSnapin, meant there would be name overlap between cmdlets in Hyper-V and cmdlets in VMware. This includes popular cmdlets, such as Get-VM and Get-VMHost.

In previous experience, I had learned about command precedence (Get-Help about_Command_Precedence). These are the precedence rules that are used when a command runs. If we have an alias, a function, a cmdlet, and a native Windows command, all with the same name, it will run the alias first when the name is entered into the PowerShell console. If we don’t have an alias with that name, but do have a function, a cmdlet, and native Windows command, then it will run the function. If the name is the same, but the command type is different, then they will always run in order from alias to function to cmdlet to native Windows command.

If we have two of the same command type and they have the same name, such as the cmdlet Get-VM, it will run the one that was added most recently. This is unless we provide the cmdlet a path. For instance, if the Hyper-V module was loaded and then the VMware PSSnapin was loaded, when we run Get-VM it will run the cmdlet from VMware. If we wanted to use the Get-VM cmdlet from the Hyper-V module, we would need to enter the full path that includes the module name: Hyper-V\Get-VM. Just because one cmdlet was loaded more recently, doesn’t mean the other one is gone.

In my mind, there’s a couple ways to fix the problem, or rather, make it easier to use cmdlets with the same name. The first option I tried, was to determine if the Add-PSSnapin cmdlet, that is used by VMware, had a -Prefix parameter. It didn’t, but had it, I could have added a prefix to all the VMware cmdlets with an option like the example below.

Add-PSSnapin -Name VMware.VimAutomation.Core -Prefix VMware

This doesn’t actually work. Read the post.

An option like this wouldn’t have clobbered my Hyper-V cmdlets, and therefore, Hyper-V’s Get-VM cmdlet would still work without a path, and VMware’s Get-VM cmdlet, for example, would have been Get-VMwareVM. The Import-Module cmdlet does have a -Prefix parameter so I could have changed the Hyper-V cmdlets to use a prefix, but I didn’t think the Hyper-V cmdlets should be the ones to suffer. I’m a PowerShell enthusiast, and I didn’t want to change the naming of Microsoft-built cmdlets, and therefore, learn them incorrectly.

Here’s what I did. I added a function to my profile ($PROFILE) that would allow me to choose which set of cmdlets would be loaded first, and which would be loaded second. The cmdlets I loaded second wouldn’t need the path to use them. This meant I could set the Hyper-V cmdlets to not need a path when I work with those, or I could set VMware cmdlets to not need a path when I work with those.

I started by declaring an empty function called Add-VMCs (Virtual Machine Cmdlets) that included a single parameter called Default.

Function Add-VMCs($Default) {

}

I started the function with an If statement. On line 2, below, it checks the parameter that has been provided when calling the function. If it matches the letter h or the letter v it will continue to line 3. If it does not, it will jump to line 7 and run the Else portion – displaying a message that nothing was changed and what parameters can be used. Line 3 and 4 remove the Hyper-V module and VMware PSSnapin, whether or not they are already loaded. While there could have been some logic to first check if they are loaded, I decided I was fine with hiding any errors that might occur (-ErrorAction SilentlyContinue) if I tried to unload a module or PSSnapin that wasn’t already loaded. I usually handle errors better than this, but I didn’t think it was necessary for this function.

The reason this is required is because if we try to load an already loaded module or PSSnapin, it won’t actually bother doing it, or at least that’s what I think is going on. This would prevent the function from ensuring the module and PSSnapin were loaded in my preferred order. Remember, if cmdlet names are the same, the most recently loaded cmdlet will be the one that is used.

Function Add-VMCs($Default) {
    If ($Default -eq 'h' -or $Default -eq 'v') {
        Remove-Module -Name Hyper-V -ErrorAction SilentlyContinue
        Remove-PSSnapin -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue

    } Else {
        Write-Output -Verbose "INFO: No changes made`r`nUse H to set Hyper-V as the default (Add-VMCs H) or use V to set VMware as the default (Add-VMCs V)."
    }
 }

Like we said previously, if the parameter is equal to the letter v or the letter h, it will remove the module and PSSnapin. The function continues to an If-ElseIf statement that begins on line 5. This bit of logic takes different actions depending on the value of the parameter. If the parameter is equal to the letter h, it will load the VMware PSSnapin and then import the Hyper-V module. This means that Get-VM would be the cmdlet associated with Hyper-V. If it is not equal to the letter h, and instead it is equal to the letter v, then it will load the Hyper-V module and then the VMware PSSnapin. This means that Get-VM would be the cmdlet associated with VMware.

Function Add-VMCs($Default) {
    If ($Default -eq 'h' -or $Default -eq 'v') {
        Remove-Module -Name Hyper-V -ErrorAction SilentlyContinue
        Remove-PSSnapin -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue
        If ($Default -eq 'h') {
            Add-PSSnapin -Name VMware.VimAutomation.Core
            Import-Module -Name Hyper-V
        } ElseIf ($Default -eq 'v') {
            Import-Module -Name Hyper-V
            Add-PSSnapin -Name VMware.VimAutomation.Core
        }
    } Else {
        Write-Output -Verbose "INFO: No changes made`r`nUse H to set Hyper-V as the default (Add-VMCs H) or use V to set VMware as the default (Add-VMCs V)."
    }
}

Here’s the function in action. At first I set the Hyper-V cmdlets to be the default on line 1. I then verify that my cmdlet is from the correct module by using the Get-Command cmdlet. Once that’s been verified, I run the Get-VM cmdlet on line 8. Then, I do it all again after I change the default to VMware. Note: If you’ve ever used VMware’s PowerCLI then you know I had to use the Connect-VIServer cmdlet to connect to a vCenter system before it would allow me to run the VMWare Get-VM cmdlet.

PS C:\> Add-VMCs -Default H
PS C:\> Get-Command Get-VM

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Get-VM                                             Hyper-V

PS C:\> Get-VM -Name Win8.1*

Name        State   CPUUsage(%) MemoryAssigned(M) Uptime   Status
----        -----   ----------- ----------------- ------   ------
Win8.1PS5.0 Running 23          1536              00:26:20 Operating normally

PS C:\> Add-VMCs V
PS C:\> Get-Command Get-VM

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Get-VM                                             VMware.VimAutomation.Core

PS C:\> Get-VM -Name Windows*

Name                 PowerState Num CPUs MemoryGB
----                 ---------- -------- --------
Windows 2003 R2 S... PoweredOff 1        1.000

Linked from here:
http://blogs.technet.com/b/heyscriptingguy/archive/2014/09/04/powertip-use-complete-name-for-powershell-cmdlet.aspx