Tag Archives: $PROFILE

Give a Parameter a Default Value (Part II)

Part I: http://tommymaynard.com/quick-learn-give-a-parameter-a-default-value-2015
Part III: http://tommymaynard.com/quick-learn-give-a-parameter-a-default-value-part-iii-2016

An old co-worker and friend contacted me last week. We hadn’t chatted since the last time I was interviewing for a job — a job I have now (well, pretty close anyway). I remember telling him I wanted a job that allowed me to work with Windows PowerShell. While he probably wasn’t surprised to hear this job did just that, he might have been, to hear I had started this site — a site using my name, and all about PowerShell. It’s a pretty serious commitment to associate your name with a technology, and continue to provide new content.

He mentioned to me that he wasn’t aware of the $PSDefaultParameterValues preference variable until he read this post. No joke, but it’s exciting to know that something I wrote was helpful for him. You see, this isn’t your average intelligence IT Pro, no, this is walking, talking IT genius. You know the kind — one of the ones you’d hire first, if you opened your own consulting business. In fact, during that same first chat, I discovered that he spoke at the Microsoft Higher-Education Conference in October 2014. He’s definitely doing something right.

As last weekend passed, I occasionally thought about that $PSDefaultParameterValues post and decided I should add something more to it, especially since I highlighted what I would consider to be the less used approach for populating this variable. First off, for those that haven’t read the previous post, the $PSDefaultParameterValues variable allows you to specify a default value for a cmdlet’s, or function’s, parameter(s).

For example, if I wanted the Get-Help cmdlet to always include the -ShowWindow parameter, then I can add that default value to the cmdlet like so:

PS C:\> $PSDefaultParameterValues
PS C:\> # Proof the variable is empty.
PS C:\> $PSDefaultParameterValues.Add('Get-Help:ShowWindow',$true)
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-Help:ShowWindow            True

With this value set, every time we use the Get-Help cmdlet, it will include the -ShowWindow parameter, even though I don’t specifically enter it at the time the command is run. In the previous example, we use the .Add() method to add a key-value pair; however, I suspect that most people will actually be more likely to use a hash table to add the cmdlet and parameter, and its new, default parameter value. Here’s the same example as above using the hash table option.

PS C:\> $PSDefaultParameterValue
PS C:\> # Proof the variable is empty.
PS C:\> $PSDefaultParameterValues = @{'Get-Help:ShowWindow' = $true}
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-Help:ShowWindow            True

Now for the reason I led with with .Add() method: If you use the hash table option above, and the variable is already populated, then it’s going to overwrite the value it’s already storing, unless we use the += assignment operator. Follow along in the next few examples under the assumption that $PSDefaultParameterValues, is already populated from the previous example.

PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-Help:ShowWindow            True

PS C:\> $PSDefaultParameterValues = @{'Get-ADUser:Server' = 'MyClosestDC'}
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-ADUser:Server              MyClosestDC

In the example above, using the = assignment operator overwrote our Get-Help ShowWindow key-value pair, as it would with any populated variable. The idea here is that we either need to assign all the parameter default values at once (when using a hash table), or use the += assignment operator to append other key-value pairs. Here’s both ways:

PS C:\> $PSDefaultParameterValues
PS C:\> # Proof the variable is empty.
PS C:\> $PSDefaultParameterValues = @{'Get-Help:ShowWindow' = $true;'Get-ADUser:Server' = 'MyClosestDC'}
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-ADUser:Server              MyClosestDC
Get-Help:ShowWindow            True

PS C:\> $PSDefaultParameterValues = $null
PS C:\> $PSDefaultParameterValues
PS C:\> # Proof the variable is empty.
PS C:\> $PSDefaultParameterValues = @{'Get-Help:ShowWindow' = $true}
PS C:\> $PSDefaultParameterValues += @{'Get-ADUser:Server' = 'MyClosestDC'}
PS C:\> $PSDefaultParameterValues

Name                           Value
----                           -----
Get-ADUser:Server              MyClosestDC
Get-Help:ShowWindow            True

That’s all there is to it. If you’re going to use a hash table and assign values to your $PSDefaultParameterValues at different times, then be sure to use the correct assignment operator, or use the .Add() method — it’s up to you. For me, I populate this variable in my profile, so overwriting it, is something in which I’m not concerned. Thanks for reading!

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.

Three Sets of Credentials for Three (or the Same) Domains

I briefly scanned this topic on Reddit’s PowerShell subreddit. I’m not really sure if this will help anyone, but the first thing I thought of was to create an advanced function that could prompt for up to three different sets of credentials, each for a different domain. It’s loosely based on a function I have in my $PROFILE that I use for a second domain. It should be noted that you don’t have to use the function for different domains. It can be used for different user accounts in the same domain—that part is up to you.

Function New-TMMultiDomainCred {
    [CmdletBinding()]
    Param (
        [switch]$Domain1,
        [switch]$Domain2,
        [switch]$Domain3
    )

    Begin {
    } #End Begin

    Process {
        Switch ($PSBoundParameters) {
            {$PSBoundParameters.Keys -contains 'Domain1'} {
                $Global:Domain1Cred = Get-Credential -Credential $null
                Write-Output -Verbose 'Domain Creds for Domain1 stored in $Domain1Cred'
            }
            {$PSBoundParameters.Keys -contains 'Domain2'} {
                $Global:Domain2Cred = Get-Credential -Credential $null
                Write-Output -Verbose 'Domain Creds for Domain2 stored in $Domain2Cred'
            }
            {$PSBoundParameters.Keys -contains 'Domain3'} {
                $Global:Domain3Cred = Get-Credential -Credential $null
                Write-Output -Verbose 'Domain Creds for Domain3 stored in $Domain3Cred'
            }
            default {
                Write-Warning -Verbose 'Parameters must be provided to use this advanced function.'
            }
        }
    } #End Process
} #End Function

about_Profiles

This post is the help rewrite for about_Profiles. While the help files for Windows PowerShell are invaluable, the idea behind a rewrite is so true beginners might even better understand the help file concepts. At times, some things discussed in the Windows PowerShell help file will not be included in a help rewrite. Therefore, it is always best to read the actual help file after reading this post. (PS3.0)

Once you start using your profile ($PROFILE), you’ll have a hard time not using it and not adding new things to it. When you have a profile (script), it runs each time you open a new Windows PowerShell session. My current profile does a number of things – it sets my location to the C:\ drive, sets various variables, set aliases, modifies the console’s window title, and creates several functions. Some of those functions allow me to connect remotely to Exchange servers, load the VMWare PowerCLI PSSnapin (without having to remember its name), use Wake-on-LAN to wake my home computer, and several others.

There are several different profiles based on the host and the user. To see your profile, type $PROFILE and press Enter in the console or Integrated-Scripting Environment (ISE). As seen in the example below, this will display the path to the profile. Line 1 is typed in the standard PowerShell console and Line 3 in the ISE.

PS C:\> $PROFILE
C:\Users\tommymaynard\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
PS C:\> $PROFILE
C:\Users\tommymaynard\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1

There are more profiles than these two and they are active under different circumstances. To see all the profiles, pipe the $PROFILE variable to the Select-Object cmdlet with the wildcard character.

PS C:\> $PROFILE | Select-Object -Property *

AllUsersAllHosts       : C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
AllUsersCurrentHost    : C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts    : C:\Users\tommymaynard\Documents\WindowsPowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\tommymaynard\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
Length                 : 82

Based on the information on the left hand side, there are different profiles based on who is using what host. While it’s not recommended to modify the profiles in the System32 directory, you should be able to tell what these do. The one on line1 is run for any user, regardless of what host (console/ISE) they use. The second one on line 2 is for any user in the current host. The third is for the current user, me, in all the hosts, and the final one is for me in the current host. These last two are the one’s you can modify.

But just by having a path stored in $PROFILE, doesn’t actually mean the file, or profile, actually exists. Use the Test-Path cmdlet to determine if the file/profile exists.

PS C:\> Test-Path $PROFILE
False

If this returns False, then you do not have a profile and so it will need to be created. You can create this file but using the New-Item cmdlet.

PS C:\> New-Item -Type File -Path $PROFILE -Force

    Directory: C:\Users\tommymaynard\Documents\WindowsPowerShell

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         8/20/2014  12:39 PM          0 Microsoft.PowerShell_profile.ps1

These two commands can be put together in an If-Else statement that will test for the file, and if not found, will create the file. Once you have a profile file created, you can open it up and start adding to it. Remember that every time you open the matching console, the profile script will run.

If (-not(Test-Path $PROFILE)) {
    New-Item -Type File -Path $PROFILE -Force
}

Here’s some quick examples of things that could be added to a profile. The first example  changes the prompt location from its default to the root of the C:\ drive.

Set-Location \

The next example sets aliases for c and gh. The first example, in line 1, will allow me to use the letter c to run the Clear-Host cmdlet and in line 2, will allow me to use the alias gh in place of typing out Get-Help.

Set-Alias -Name c -Value Clear-Host
Set-Alias -Name gh -Value Get-Help

These next example allow me to set variables inside my profile. This will allow me to use $DCs to return DC01, DC02, DC03, allow me to use $hosts to return the path of my hosts file, and allow me to return all my web and data servers by using the $AppServers variable. Noticed that $AppSevers is a mulit-dimentional array. Use $AppServers[0] to return the web servers and $AppServers[1] to return the data servers.

Set-Variable -Name DCs -Value 'DC01','DC02','DC03'
Set-Variable -Name hosts -Value "$env:SystemRoot\System32\drivers\etc\hosts"
Set-Variable -Name AppServers -Value @(('web01','web02','web03'),('data01','data02','data03'))

You can also create functions. This function allows me to type Add-VMC to load the VMware PSSnapin. For me, it’s easier to remember this short function name than remembering the PSSnapin name or having to type out Get-PSSnapin -Registered to find the name.

Function Add-VMC {
	Add-PSSnapin VMware.VimAutomation.Core
}

Start using your $PROFILE today and everything you can to help personalize your PowerShell experience. Keep in mind that profiles do not exist in remote sessions.

Learn More

This information, and more, is stored in the help file about_Profile that comes with Windows PowerShell. This information can be read by typing any of the commands below. The first example will display the help file in the Windows PowerShell console, the second example will open the full help in it’s own window, the third example will send the contents of the help file to the clipboard (so it can be pasted into Word, Notepad, etc.), and the fourth example will open the help file in Notepad.

PS C:\> Get-Help about_variables
PS C:\> Get-Help about_variables -ShowWindow
PS C:\> Get-Help about_variables| clip
PS C:\> Notepad $PSHOME\en-us\about_Variables.help.txt

Clear-Host, Without Clearing the Host

After you read this, read part 2 (and download the TMModule)

I use the Clear-Host cmdlet alias, cls, throughout the day to clear out whatever typing I have inside my Windows PowerShell console. It does its job well, but recently I’ve wanted it to work differently. I wanted it to appear that the console host has been cleared, but still allow me to scroll back up to see what was on the screen before it was cleared. I started playing around with the console class, [Console]. While not necessary, this can also be written using the namespace, System, such as [System.Console]. I like the idea of being as complete as possible and so you’ll see me use the namespace even though it’s not necessary.

Before I could write something reusable, such as a function, I had to figure out if what I wanted to accomplish, was even possible. I knew I was working with [System.Console] and so I piped that to Get-Member, but it returned the methods and properties of System.RuntimeType, seen below.

PS C:\> [System.Console] | Get-Member

    TypeName: System.RuntimeType

I struggled for a moment until I remembered an article I had read on using static classes. I found that page again, http://technet.microsoft.com/en-us/library/dd347632.aspx, and was quickly reminded that using the -Static parameter of the Get-Member cmdlet would get me the correct results.

PS C:\> [System.Console] | Get-Member -Static

    TypeName: System.Console

Running the command above produces the TypeName as shown, but it also produces all the methods and properties. I started looking over the properties and a couple about the cursor quickly caught my eye, especially the CursorTop property. After the Get-Member command from above, and based on the results of returning the CursorTop property, my cursor was positioned at line 59 inside my console, as can been seen in the example below on line 2. I cleared the screen, and beginning on line 4 below, I reran the command three more times. Each time, it gave me the location where the cursor was last positioned.

PS C:\> [System.Console]::CursorTop
59
PS C:\> cls
PS C:\> [System.Console]::CursorTop
1
PS C:\> [System.Console]::CursorTop
3
PS C:\> [System.Console]::CursorTop
5
PS C:\>

I decided I would assign the value, 0, to the CursorTop property and suddenly I was writing over the text on the top line. Take a close look at line 1 below.

PS C:\> blahblahConsole]::CursorTop
59
PS C:\> cls
PS C:\> [System.Console]::CursorTop
1
PS C:\> [System.Console]::CursorTop
3
PS C:\> [System.Console]::CursorTop
5
PS C:\> [System.Console]::CursorTop = 0

I could move my cursor, great, but this wasn’t exactly what I wanted. What I wanted was to push that scroll bar down so that anything that was already on the screen was pushed off the top of my console, and all that was left was my PowerShell prompt. I still believed there was a way to do this and so I spent a little more time looking over the properties. I found four that began with Window – WindowHeight, WindowLeft, WindowTop, and WindowWidth – and began to experiment with them. I didn’t suspect I’d be doing anything with the height and width but I thought I would check out their values anyway – 50 and 120, respectively.

PS C:\> [System.Console]::WindowHeight
50
PS C:\> [System.Console]::WindowWidth
120
PS C:\>

WindowLeft didn’t seem to be that important, because no matter how much was typed before I entered [System.Console]::WindowLeft, the property value was still set to 0. Then I entered in [System.Console]::WindowTop and it was also 0 every time. Then it dawned on me, what if I changed its value like I did with CursorTop. I tried it and my scroll bar started jumping all over. I’m getting close!

PS C:\> [System.Console]::WindowTop = 10
PS C:\> [System.Console]::WindowTop = 200
PS C:\> [System.Console]::WindowTop = 2000
PS C:\> [System.Console]::WindowTop = 0

We know the CursorTop value changes, so what would happen if we set the value of WindowTop to CursorTop? I tried it, and it worked!

PS C:\> [System.Console]::WindowTop = [System.Console]::CursorTop
PS C:\>

I thought I was done when I took another moment and scanned over the methods. I found one called SetWindowPosition. Instead of simply assigning a new value to the property WindowTop, I decided I would use the method to do the work for me. I eventually ran both of these options through the Measure-Command cmdlet and determined that there was no gain in speed by using one option over the other.

So, once I knew what to do, I opened my profile ($PROFILE) and created an empty function. For whatever reason, I called it clx thinking that this would be a good option for me. Turns out that while clx has little meaning, I was able to quickly remember it and start using it right away. Now, every time I want it to appear that I’ve cleared my host, but didn’t really, I type clx and press Enter.

Function clx {
    [System.Console]::SetWindowPosition(0,[System.Console]::CursorTop)
}

I added one additional feature to this function as is seen in the example below. This option allowed me to run the clx function and leave the last n number of rows on the screen. Try it out by ensuring your have some output in your console and then entering clx 2. This will “clear” the console screen but still allow you to view the last two rows without scrolling back up. Try it and it may make more sense.

Function clx($SaveRows) {
    If ($SaveRows) {
        [System.Console]::SetWindowPosition(0,[System.Console]::CursorTop-($SaveRows+1))
    } Else {
        [System.Console]::SetWindowPosition(0,[System.Console]::CursorTop)
   }
}

Here’s a video of the function in action. The first thing we do is return 5 processes and then 5 services. Then we use cls and notice that we cannot scroll back up to see what was cleared. This is the typical behavior. When we add the processes and services back, and then use the clx function, we can see that we have the option to scroll back up and see what was on the screen, before we “cleared” it.

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

Implicit Remoting and the Exchange Cmdlets

I work in an environment where we administer Microsoft Exchange 2010 and I’m proud to say that I don’t have the Exchange Management Tools installed on my laptop. Now, that doesn’t mean I don’t sometimes RDP into an Exchange box, but it does force me to forgo that and use Windows PowerShell when I want to do something quicker than to RDP, log on, and open the EMC. One of the first things I added to my $PROFILE, after writing it for use in a script, was a function that would establish a PSSession to one of the Exchange servers. This allows me to run Exchange-specific cmdlets without leaving my laptop and without having the Exchange Management Tools installed.

The first thing I started with was an empty function that I called New-ExchangeSession.

Function New-ExchangeSession {

}

I had a few requirements for this project. One, I didn’t want to rely on connecting to the same Exchange server each time I created a PSSession. If for some reason it wasn’t available, I would have a problem connecting… in addition to a non-responsive Exchange server. Two, I didn’t want to hard code my Exchange server list in either my script (bad!) or in an external file (not as bad, but not great). What I did was make use of the Get-ADGroupMember and the Get-Random cmdlets to return the names of the Exchange Servers from an Active Directory group in which they were all members, and then randomly select one.

I did this as part of Do-Until loop. In line 3, the first thing it does is acquire the members of the group, ExchangeServers, using the Get-ADGroupMember cmdlet included in the Active Directory module. If you’re running PowerShell 3.0 or greater it will load this module automatically. If you’re not, then you’ll have to add it yourself using the Import-Module cmdlet before beginning the Do-Until loop.

Using dotted notation (.Name) we return only the Name property of the group members. Once collected, the Get-Random cmdlet is used to randomly select one of the names and then assign it to the $ExchServ variable.

Function New-ExchangeSession {
    Do {
        $ExchServ = Get-Random (Get-ADGroupMember -Identity 'ExchangeServers').Name
    }
    Until (Test-Connection -ComputerName $ExchServ -Count 1 -Quiet)
}

If you’re using something older than 3.0, and you really shouldn’t be, you’ll find the dotted notation version just doesn’t work as expected. In that case, you will have to handle this in a more procedural way as seen in the example below. Regardless of which way you get a server name and assign it to the $ExchServ variable, it must reply to a ping, by use of the Test-Connection cmdlet in line 5 above, and line 7 below. This is the conditional check of Do-Until loop. If Test-Connection returns True, it will break out of the Do-Until. If it returns False, it will randomly select another server and try again.

Function New-ExchangeSession {
    Do {
        $ExchServ = Get-ADGroupMember -Identity 'ExchangeServers'
        $ExchServ = Get-Random $ExchServ
        $ExchServ = $ExchServ.Name
    }
    Until (Test-Connection -ComputerName $ExchServ -Count 1 -Quiet)
}

We start to create the PSSession once we have an Exchange server name chosen and verified it is reachable across the network. This is done by creating a variable, $Session on line 7 below, that will store the session information. We then use that session information as part of our Import-PSSession cmdlet that brings the Exchange cmdlets down to our local computer. The final, informational message on line 9 simply indicates the Exchange server in which we’ve connected.

Function New-ExchangeSession {
    Do {
        $ExchServ = Get-Random (Get-ADGroupMember -Identity 'ExchangeServers').Name
    }
    Until (Test-Connection -ComputerName $ExchServ -Count 1 -Quiet)

    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$ExchServ.mydomain.com/powershell" -Authentication Kerberos
    Import-PSSession -Session $Session -CommandName * -FormatTypeName * | Out-Null
    Write-Output "Connected to $ExchServ"
}

You will often, if not always, receive a warning about some of the imported commands may have unapproved verbs. To view your active session, use the Get-PSSession cmdlet, and to close the session, use the Remove-PSSession -Id  #, where # equals the Id number returned from the Get-PSSession cmdlet.