Category Archives: Quick Learn

Practical examples of PowerShell concepts gathered from specific projects, forum replies, or general PowerShell use.

PSMonday #1: Monday, May 2, 2016

Topic: Setting Multiple Variables at the Same Time

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

Welcome to the first PowerShell Monday.

Let’s get started: The first example shows one way to set, or assign, a value to a variable in Windows PowerShell. In this example, $p is assigned the string value ‘Welcome to Monday’.

$p = 'Welcome to Monday.'

Knowing that, what do you think this command accomplishes?

$a = $b = $c = 'string'

# Answer further below…








# Keep going…








# Keep going…








# Keep going…








After running that command, here are the values stored in $a, $b, and $c.

# The command was $a = $b = $c = 'string'.
$a
$b
$c

string
string
string

A command such as this, although not often seen, sets all three variables to the same value. Knowing this in an option, allows us to initialize a group of variables to the same value, at the same time. While you can do this in three separate commands, as the next two examples show, you can complete the same thing, as part of a single command.

# The line break is a command separator in PowerShell.
$x = 'string'
$y = 'string'
$z = 'string'
# The semicolon is a command separator, too.
$x = 'string'; $y = 'string'; $z = 'string'

Bonus: What do you think this command does?

$m,$n,$o = 'string'

You probably won’t ever see this command — I haven’t — but why not know what it does. In this example, only $m is assigned the value of ‘string’. The other two — $n and $o, in this example — lose any value they may have been storing. Again, I haven’t seen this, and I wouldn’t recommend you use it. I suspect that it would end up being confusing, compared to the traditional methods of assigning and removing values from variables. I said it was a bonus; I didn’t say it was a useful.

Second Bonus: What about this command; what do you think happens here?

$e,$f,$g = 'Monday','Tuesday','Wednesday'

I’ve never seen this command used either, but I can see how this one might be useful to use, without too much confusion. I still won’t recommend it, though. In this example, we end up assigning ‘Monday’ to $e, ‘Tuesday’ to $f, and ‘Wednesday’ to $g. If we had a fourth variable, such as $h, and it had a value in it, the value would be removed, such as we saw in the first bonus. This is of course, unless there was another value after ‘Wednesday’.

That’s it for the first PSMonday. Hope you’ve learned something new, and we’ll do this again next week.

PowerShell Monday Table of Contents

I’m trying something new at work dubbed PSMonday, as in—and, I hope you could’ve guessed this—PowerShell Monday. I’m writing up quick and easy-to-read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work are consistently learning something new about Windows PowerShell. This includes both my team, and our Operations Team (Edit: And, now our VMware Team, too. Edit2: Our Windows DBA is reading along, as well.).

Well, I thought about it over the weekend, and I can’t figure out why I wouldn’t also include the lesson right here. If you’re into PowerShell too, then I would encourage you to do a PSMonday at your place of work, as well. Help people help themselves. These posts are sorted beginning with the oldest PSMonday at the top. Scroll to the bottom to locate and read the newest PowerShell Monday, and check out the ones in between.

PSMonday #1: Monday, May 2, 2016
Topic: Setting Multiple Variables at the Same Time

PSMonday #2: Monday, May 9, 2016
Topic: Best Way to Filter the Results of Your Commands

PSMonday #3: Monday, May 16, 2016
Topic: Quotes and Strings, and Their Rules

PSMonday #4: Monday, May 23, 2016
Topic: Add and Concatenate Numbers and Strings

PSMonday #5: Monday, May 30, 2016
Topic: Splatting and Hash Tables

PSMonday #6: Monday, June 6, 2016
Topic: Splatting and Hash Tables Continued

PSMonday #7: Monday, June 13, 2016
Topic: Test-NetConnection

PSMonday #8: Monday, June 20, 2016
Topic: Less Used Variable Properties

PSMonday #9: Monday, June 27, 2016
Topic: Less Used Variable Properties Continued I

PSMonday #10: Monday, July 4, 2016
Topic: Less Used Variable Properties Continued II

PSMonday #11: Monday, July 11, 2016
Topic: PowerShell Remoting

PSMonday #12: Monday, July 18, 2016
Topic: PowerShell Remoting Continued

PSMonday #13: Monday, July 25, 2016
Topic: PowerShell Remoting Continued II

PSMonday #14: Monday, August 1, 2016
Topic: PowerShell Remoting Continued III

PSMonday #15: Monday, August 8, 2016
Topic: PowerShell Remoting Continued IV

PSMonday #16: Monday, August 15, 2016
Topic: PowerShell Remoting Continued V

PSMonday #17: Monday, August 22, 2016
Topic: PowerShell Remoting Continued VI

PSMonday #18: Monday, August 29, 2016
Topic: Get-Member

PSMonday #19: Monday, September 5, 2016
Topic: Get-Member Continued

PSMonday #20: Monday, September 12, 2016
Topic: Get-Member Continued II

PSMonday #21: Monday, September 19, 2016
Topic: Get-Member Continued III (and Arrays)

PSMonday #22: Monday, September 26, 2016
Topic: Get-Member Continued IV

PSMonday #23: October 3, 2016
Topic: Commands, Cmdlets, and Functions

PSMonday #24: October 10, 2016
Topic: $PSDefaultParameterValues

PSMonday #25: October 17, 2016
Topic: $PSDefaultParameterValues II

PSMonday #26: October 24, 2016
Topic: PowerShell’s Importance

PSMonday #27: October 31, 2016
Topic: Introduction to the Language Constructs

PSMonday #28: November 7, 2016
Topic: If, If-Else, If-ElseIf

PSMonday #29: November 14, 2016
Topic: If, If-Else, If-Else II

PSMonday #30: November 21, 2016
Topic: If, If-Else, If-Else III

PSMonday #31: November 28, 2016
Topic: Switch Statement

PSMonday #32: December 5, 2016
Topic: Switch Statement II

PSMonday #33: December 12, 2016
Topic: Switch Statement III

PSMonday #34: December 19, 2016
Topic: Switch Statement IV

PSMonday #35: December 26, 2016
Topic: Foreach

PSMonday #36: January 2, 2017
Topic: Foreach II

PSMonday #37: January 9, 2017
Topic: ForEach-Object

PSMonday #38: January 16, 2017
Topic: ForEach-Object II

PSMonday #39: January 23, 2017
Topic: For

PSMonday #40: January 30, 2017
Topic: Do-While

PSMonday #41: February 6, 2017
Topic: Do-Until

PSMonday #42: February 13, 2017
Topic: While

PSMonday #43: February 20, 2017
Topic: Reusable Code I

PSMonday #44: February 27, 2017
Topic: Reusable Code II

PSMonday #45: March 6, 2017
Topic: Reusable Code III

PSMonday #46: March 13, 2017
Topic: Reusable Code IV

PSMonday #47: March 20, 2017
Topic: Reusable Code V

PSMonday #48: March 27, 2017
The (Online) End of PowerShell Monday

PowerShell Nested Switch Statements

There was a Reddit post recently that gave me an opportunity to try something I’m not sure I’ve tried before: nested switch statements—-switch statements inside switch statements. So we’re on the same page, I’ve written an example of a basic, non-nested switch. It provides some of the same functionality as an If-ElseIf. I’ll typically use a switch statement instead of an If-ElseIf when there’s three or more outcomes. It’s easier to read, and it looks cleaner.

In the example below, a specific phrase will be written if the value in the $Fruit variable is apple, banana, or pear. If it’s not one of those, it’ll take the default action. In the case below, the default action indicates that an apple, banana, or pear wasn’t chosen. Switch statements will sometimes be called case statements in other languages.

$Fruit = 'banana'
Switch ($Fruit) {
    'apple' {'You chose apple.'; break}
    'banana' {'You chose banana.'; break}
    'pear' {'You chose pear.'; break}
    default {'You didn''t choose an apple, banana, or pear.'}
}

As a part of my nested switch statement testing, I wrote this example using automobile makes and models. The outermost switch statement makes a determination of which nested switch statement to execute.

$Make = 'Ford'
$Model = 'Escape'
 
Switch ($Make) {
    'Ford' {
        Switch ($Model) {
            'Mustang' {Write-Output -InputObject 'You selected the Ford Mustang.'; break}
            'Escape' {Write-Output -InputObject 'You selected the Ford Escape.'; break}
            default {Write-Output -InputObject "You selected Ford, however, the $Model isn't a valid model for this make."; break}
        }
    }
    'Chevy' {
        Switch ($Model) {
            'Corvette' {Write-Output -InputObject 'You selected the Chevy Corvette.'; break}
            'Camaro' {Write-Output -InputObject 'You selected the Chevy Camaro.'; break}
            default {Write-Output -InputObject "You selected Chevy, however, the $Model isn't a valid model for this make."; break}
        }
    }
    'Dodge' {
        Switch ($Model) {
            'Charger' {Write-Output -InputObject 'You selected the Dodge Charger.'; break}
            'Viper' {Write-Output -InputObject 'You selected the Dodge Viper.'; break}
            default {Write-Output -InputObject "You selected Dodge, however, the $Model isn't a valid model for this make."; break}
        }
    }
}

Should we take this further—-a switch statement inside a switch statement inside a switch statement? I’m not going to lie, it started to get confusing and so my first recommendation would be to try and steer clear of the multi-nesting this deep (3 levels). I’ve removed some of the example above—-Chevy and Dodge—-in my example below and focused on the Ford (pun, absolutely intended). To help, I’ve included comments on the closing brackets. This multi-nested switch statement will return different values based on the make, the model, and the year, too.

$Make = 'Ford'
$Model = 'Escape'
$Year = '2016'
  
Switch ($Make) {
    'Ford' {
        Switch ($Model) {
            'Mustang' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Mustang."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Mustang"; break}
                    default {Write-Output -InputObject "You selected the Ford Mustang with a non-matching year."; break}
                } # End Mustang Year Switch.
            } # End Mustang Switch.
            'Escape' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Escape."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Escape."; break}
                    default {Write-Output -InputObject "You selected the Ford Escape with a non-matching year."; break}
                } # End Escape Year Switch.
            } # End Escape Switch.
            default {Write-Output -InputObject "You selected a Ford; however, the $Model isn't a valid model for this make."}
        } # End Ford Model Switch.
    } # End Ford Switch.
    default {Write-Output -InputObject 'Sorry, we''re only dealing with Fords (at the moment).'}
} # End Make Switch.

So I can track it down later, here’s the post I read on Reddit that influenced this post. It includes the link to my possible, partial solution; however, that direct link is here: http://pastebin.com/KRxRb1VM.

Update: It has occurred to me that it might be easier to follow this thrice nested switch without all the comments. I’ve removed those below.

$Make = 'Ford'
$Model = 'Escape'
$Year = '2016'
  
Switch ($Make) {
    'Ford' {
        Switch ($Model) {
            'Mustang' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Mustang."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Mustang"; break}
                    default {Write-Output -InputObject "You selected the Ford Mustang with a non-matching year."; break}
                }
            }
            'Escape' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Escape."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Escape."; break}
                    default {Write-Output -InputObject "You selected the Ford Escape with a non-matching year."; break}
                }
            }
            default {Write-Output -InputObject "You selected a Ford; however, the $Model isn't a valid model for this make."}
        }
    }
    default {Write-Output -InputObject 'Sorry, we''re only dealing with Fords (at the moment).'}
}

Linux Prompt on Windows – Part I

I already have an answer to the question, “Why?” Because, I can. At some point in the last few days, I decided I would attempt to replicate the Linux prompt in PowerShell. So I did it, and I thought I would share it. I’ve broken this up a little with the full function toward the bottom of this evening’s post.

In this first section, we’ve created the prompt function’s basic structure and added our first bit of code. This code determines if the user that started the Windows PowerShell session is an administrator or not. If they are, they get the # symbol as part of the prompt, and if they’re not, they get the $ symbol.

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

The next addition includes the logic to determine how to write the path inside the prompt. If the current path is C:\Users\<CurrentUser>, it enters a tilde (~). If the path includes the C:\Users\<CurrentUser> path, in addition to one or more directories, it will enter a tilde and the other directory or directories, such as ~/Desktop, or ~/Favorites/Links. In the last case, it’ll simply list the current path after replacing the backslashes with forward slashes and dumping the drive letter and colon. In all instances, in fact, all (Windows) backslashes, will be replaced with (Unix/Linux) forward slashes.

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

    If ($PWD.Path -eq $env:USERPROFILE) {
        $Location = '~'
    } ElseIf ($PWD.Path -like "*$env:USERPROFILE*") {
        $Location = $PWD.Path -replace ($env:USERPROFILE -replace '\\','\\'),'~' -replace '\\','/'
    } Else {
        $Location = "$(($PWD.Path -replace '\\','/' -split ':')[-1])"
    }
}

The last section is where the prompt function actually writes the prompt to the screen. It’s the simple combination of the current user, an @ character, the computer name, the path, and the proper symbol (# or $). As you may notice, I’ve written the code to force the case of the current user and computer to lowercase.

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

    If ($PWD.Path -eq $env:USERPROFILE) {
        $Location = '~'
    } ElseIf ($PWD.Path -like "*$env:USERPROFILE*") {
        $Location = $PWD.Path -replace ($env:USERPROFILE -replace '\\','\\'),'~' -replace '\\','/'
    } Else {
        $Location = "$(($PWD.Path -replace '\\','/' -split ':')[-1])"
    }

    "$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Location $Symbol "
}

In closing, I’ve included a gif of the prompt and an example of moving though the directory structure. While I typically only use full cmdlet names in my posts, this example does include aliases for the Set-Location cmdlet (cd, sl, and even chdir). Due to this function being saved in my profile script, this prompt displays immediately when a new console is opened, and every time after that. As someone that’s written a few different prompt functions, this one’s a keeper. I already can’t imaging switching back. On that note, you might not want to test it out.

duplicate-the-linux-prompt01

tommymaynard@srv01 / # $PWD.Path
C:\
tommymaynard@srv01 / # cd C:\Windows\
tommymaynard@srv01 /Windows # cd .\System32\
tommymaynard@srv01 /Windows/System32 # chdir ..
tommymaynard@srv01 /Windows # chdir ..
tommymaynard@srv01 / # sl /users
tommymaynard@srv01 /users # sl /users/tommymaynard2 # Not my current user.
tommymaynard@srv01 /users/tommymaynard2 # cd ..
tommymaynard@srv01 /users # cd C:\Users\tommymaynard\ # My current user.
tommymaynard@srv01 ~ # cd .\Desktop\
tommymaynard@srv01 ~/Desktop # cd ..
tommymaynard@srv01 ~ # cd .\Favorites\Links\
tommymaynard@srv01 ~/Favorites/Links # sl /
tommymaynard@srv01 / # cd /users
tommymaynard@srv01 /users # chdir \
tommymaynard@srv01 / #

Update1: A day later and I still love my new PowerShell prompt. I’ve made a minor change, however. The last line in the function is now the following three lines. It stores the prompt in a variable called $Prompt, modifies the title of the window to the $Prompt value, and then writes the prompt. In the past my window’s title indicate the computer name. Now it indicates that information, as well as the current user, the location on the current drive, and whether or not I’m running the session as an administrator.

...
	$Prompt = "$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Location $Symbol "
	$Host.UI.RawUI.WindowTitle = $Prompt
	$Prompt
}

Update2: It occurred to me today that by entering the tilde character, such as Set-Location -Path ~, it moves you to the location stored in the Home property of the FileSystem PSProvider. Because of this, I opted to add another line, just before the beginning of the prompt function. Setting this property to $env:USERPROFILE, allows me to quickly move to C:\Users\<CurrentUser>. I should note that this property is sometimes already set. In those cases, this is just a precaution that the ~ character will move you back home.

(Get-PSProvider -PSProvider FileSystem).Home = $env:USERPROFILE
Function Prompt {
...
}

In case you want to take this prompt function for a spin, and you do, here’s everything. Copy and paste it to your console and hit Enter a couple times and try it out. If you hate it, close and reopen the PowerShell console and it never existed. Cheers.

(Get-PSProvider -PSProvider FileSystem).Home = $env:USERPROFILE
Function Prompt {
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'
    } Else {
        $Symbol = '$'
    }
 
    If ($PWD.Path -eq $env:USERPROFILE) {
        $Location = '~'
    } ElseIf ($PWD.Path -like "*$env:USERPROFILE*") {
        $Location = $PWD.Path -replace ($env:USERPROFILE -replace '\\','\\'),'~' -replace '\\','/'
    } Else {
        $Location = "$(($PWD.Path -replace '\\','/' -split ':')[-1])"
    }

	$Prompt = "$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower()) $Location $Symbol "
	$Host.UI.RawUI.WindowTitle = $Prompt
	$Prompt
}

 

Check for Active Directory User in Function Parameters

I’m not sure what it is, but this might’ve been the longest I’ve gone without writing. It’s not that I’ve stopping thinking PowerShell, it’s that I’m so consumed, I’ve had a hard time focusing. My wife calls it my adult ADD. It was just last week that I attended my second PowerShell + DevOps Global Summit (that’s the new name). I learned a great deal and I had a great time. Thanks again to PowerShell.org for the contest and free 4-day pass, my employer for picking up the flight, lodging, and food, my parents for their help, and of course to my wife! The weather in Bellevue, WA was perfect and the summit was a huge success. Much gratitude to all those involved in the planning and execution.

It wasn’t long ago that I had a post about comparing Active Directory group memberships. I wanted a pleasant way to visualize and compare the group memberships of two AD users. We had a ticket earlier today, at the office, where I went back to that post and gathered the commands, so I could use them again. I knew I’d need it, I just didn’t know I’d need it so soon. I sent the output results as an image to a coworker, and it wasn’t but a few seconds later that he was interested in what was used to gather that output: it was my modified compare. Here’s an edited version of the image I sent. This image differs from the original post in that I added the users’ SamAccountNames as the property names in my collection. This is why they’re blurred out this time around.

check-for-active-directory-user-in-function-parameters01

I mentioned to him that I’d write the commands into a wrapper function. While that’s nearly done, I asked myself this question: When should I check if the users exist in Active Directory? Do I do it as part of the function (inside the Begin block, for instance), or do I check at the time the parameter is accepted, as part of a ValidateScript validation?

I’m here today to say that making it a part of the ValidateScript validation attribute is acceptable. While it has the potential to cause an error condition and dump red text to the console, that’s fine. People good at Windows PowerShell read these errors — they’re PowerShell’s errors, and we’re just allowing them to occur. While I can catch these in the function, PowerShell doesn’t always do this for us natively, so I as I see it, it’s not always necessary. Knowing the audience for your tool can help you with this determination. Do you want them to learn PowerShell and get comfortable with the native errors, or does it make sense to protect them from seeing these? It’s up to you. I’m okay with allowing my tools to display some of the native errors, especially since we’re talking about my team using these tools, and not a lower-tiered group. Again, you have to consider who will use your tools, and how they’ll handle the red text.

The test function I wrote, and some error results, can been seen below. In my mind, there were two error possibilities: the computer where the function is invoked doesn’t have the ActiveDirectory module, or one, or both, of the users don’t exist in Active Directory. Again, in many cases, these standard PowerShell errors can be delivered without the need to hide them from the user of the function. Here’s a test function now.

Function Test-ADUser {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true,Position=0)]
        [ValidateScript({Get-ADUser -Identity $_})]
        [string]$User1,

        [Parameter(Mandatory=$true,Position=1)]
        [ValidateScript({Get-ADUser -Identity $_})]
        [string]$User2
    )

    Write-Output -InputObject "Success on both parameters."
}

This first image below, shows the error condition when the ActiveDirectory module isn’t available on the system where the function is invoked. It displays the standard, I-don’t-recognize-what-you-entered error: “Get-ADUser is not recognized…”

check-for-active-directory-user-in-function-parameters02

In this image, we can see the error message that occurs when one of the supplied users doesn’t exist in Active Directory. It won’t get this far unless the ActiveDirectory module is on the system where this function is invoked.

check-for-active-directory-user-in-function-parameters03

So in the end, consider when writing your tools, if a standard PowerShell error is acceptable or not. I think that there’s times it’s suitable, depending on the error and the group using the tool.

Keep an Attached Drive Active

I recently received two drives sent via FedEx for a rather large data copy. Maybe I haven’t been looking, but I’ve never seen anything like these drives before, although it wasn’t too difficult to believe they existed. They each hold 6TB and have a 10-key keypad on the drive case where you have to punch in a PIN to unlock the drive. Neat, except that the drives also have a timeout period in which they automatically lock. To unlock each drive, providing there’s been no disk activity, as that’s what starts the timeout counter, is a trip to the data center to enter the PIN again.

keep-an-attached-drive-active01

A coworker and I quickly arrived at the same idea in order to keep the drives active. I bet you just thought of an idea, too. It’s extremely simplistic, but that’s typically the road on which I travel. In the simple Do-While loop below, we created a file on each of the attached drives. In my example, the loop slept for eight minutes after it created the file. Then it removed the files and slept for another 10 seconds before repeating.

Do {
    New-Item -Path E:\keepalivefile.txt,F:\keepalivefile.txt -ItemType File | Out-Null
    Start-Sleep -Seconds 480 # equal to 8 minutes
    Remove-Item -Path E:\keepalivefile.txt,F:\keepalivefile.txt
    Start-Sleep -Seconds 10
}
While ($true)

That’s all I’ve got tonight, other than to say, I have no idea what I would’ve done if I didn’t know Windows PowerShell. Seriously, how would I have been able to guarantee the drives wouldn’t lock, besides to manually create the disk activity? Keeping File Explorer open wasn’t enough, I tried. PowerShell to the rescue. Again.

An Enhanced Active Directory Group Membership Compare

Like everyday, there was a Windows PowerShell question on the Internet today. While I may not have provided everything the original poster was asking for, I rather like what I had accomplished. Being that I have a website that resolves around PowerShell 100% of the time, it seemed like a good little chunk of code to share. I’m going to want it one day, I just know it, and I’m not going to want to rewrite it.

What we’ve done is compared the Active Directory (AD) group memberships of two different users using Get-ADPrincipalGroupMembership and the Compare-Object cmdlet. Stay tuned, this isn’t the standard Compare-Object output you might be used to seeing. I’m doing this much like the original forum question, so l stored the SamAccountName of two AD users, in two different variables.

PS> $FirstUser = 'tommymaynard'
PS> $SecondUser = 'lanceandrews'

With the variables set, we’ve used them as part of our Get-ADPrincipalGroupMembership commands below. In this example, you can see two ways of accomplishing the same thing — useful stuff, really.

PS> $FirstUserGroups = Get-ADPrincipalGroupMembership $FirstUser | Select-Object -ExpandProperty Name
PS> $SecondUserGroups = (Get-ADPrincipalGroupMembership $SecondUser).Name

With this second set of variables assigned, we’ve set up the command below to obtain the differences and store those in a variable we’ve called $Difference. Including the -IncludeEqual parameter has allowed us to be able to see the group memberships that both users share.

PS> $Difference = Compare-Object -ReferenceObject $FirstUserGroups -DifferenceObject $SecondUserGroups -IncludeEqual

Next, we put together some calculated properties from our $Difference variable. If calculated properties are new to you, be sure to do some additional research. They can be used to simply rename properties, but also, like I’ve demonstrated below, they can include conditional logic. You might consider copying and pasting the example somewhere else, so you can better digest what’s happening. We’ve created three calculated properties: the first is renaming the InputObject property to Group. The next two properties write one of two things: either that the user is a member, or that they’re not, by entering two, side-by-side dashes. See the image further below.

PS> $Difference | Select-Object @{N='Group';E={$_.InputObject}},@{N='First User';E={If ($_.SideIndicator -eq '<=' -or $_.SideIndicator -eq '==') {'Member'} Else {'--'}}},@{N='Second User';E={If ($_.SideIndicator -eq '=>' -or $_.SideIndicator -eq '==') {'Member'} Else {'--'}}}

This modification of the standard Compare-Object output reminds us which user is which. The standard output uses arrows. A left arrow indicates the first user is a member and a right arrow indicates the second user is a member. Because we’re using the -IncludeEqual parameter, we can distinguish when both users are a member of the same AD group, and that, without the double equal sign, as the indicator.

compare-active-directory-membership-between-two-users01

Neat. I’ll be back to visit you later, modified Compare-Object command. Thanks for reading, everyone.

Remove and Add Users to an Active Directory Group

The more people that recognize that I’ve made an investment in Windows PowerShell, the more these posts write themselves. I was recently asked by a previous colleague to help them make some modifications in Active Directory (AD). They needed to strip out all the users from a single AD group, and then add a bunch of them back that were, conveniently, stored in a text file. While you can compare the current users in the group with the ones you want to add, this post will assume it’s suitable to momentarily remove all the users from a specific AD group.

When I completed this, out of the kindness of my heart, and because I want to promote and teach PowerShell whenever possible, I sent the previous colleague an email with the five steps I had completed. They might be useful for others as well, so here we are. I think I’m getting a lunch out of this, too. While it might not be Chipotle, I like food, so I’m sure it’ll be suitable.

1. Export Users in the AD Group: This command will return the current members of the VPN-Users group and store them in a Csv file. While this will allow us to compare our final results, its original purpose was as my safety net in case I made a mistake and wanted to make the previous members, members again.

PS> $GroupName = 'VPN-Users'
PS> Get-ADGroupMember -Identity $GroupName | Export-Csv -Path C:\VPN-Users01Pre.csv -NoTypeInformation

2. Remove Users from Group: This next command removes all the members of the group (which I had checked were only users). It did this by piping each member of the group to a ForEach-Object loop that ran Remove-ADGroupMember against the current user. Had there been other object types, you’d have to use a Where-Object command in between these two commands (command 1 | Where-Object… | command 2), filtering on the ObjectClass. The Remove-ADGroupMember cmdlet is using the -Confirm parameter with the $false value, so there’s no need for manual confirmation of each removal.

PS> Get-ADGroupMember -Identity $GroupName | Foreach-Object {Remove-ADGroupMember -Identity $GroupName -Members $_ -Confirm:$false}

3. Add Users to Group: After I allowed a few moments for replication, I went ahead and added all the users in the text file I had been supplied by using the Get-Content command and piping each entry to the ForEach-Object and Add-ADGroupMember cmdlets. It threw two errors when it wasn’t able to find a couple users in AD. While I didn’t, we could’ve written in precautions in this command to avoid this error, or programmatically fixed the file prior to this command.

PS> Get-Content -Path C:\UserNames.txt | Foreach-Object {Add-ADGroupMember -Identity $GroupName  -Members $_}

4. Export Users in the AD Group: Now that we have all the old group members removed, and the new ones added, we can create a new export of the group members for comparison. This isn’t necessary, but helps to see who was removed and added, if we desire that kind of information and comparison.

PS> Get-ADGroupMember -Identity $GroupName | Export-Csv -Path C:\VPN-Users02Post.csv -NoTypeInformation

5Compare Before and After Group Memberships: The next command will reach into the SamAccountName column inside both Csv files and compare them. Results that indicate <= mean they are in the file on the left (the file as the value to -ReferenceObject parameter), and results that indicate => mean they are only in the file on the right (-DifferenceObject parameter). Don’t forget, you can always use the -IncludeEqual parameter if you want to see which users where there before and after.

Compare-Object -ReferenceObject ((Import-Csv -Path C:\VPN-Users01Pre.csv).SamAccountName) -DifferenceObject ((Import-Csv -Path C:\VPN-Users02Post.csv).SamAccountName)

There ya go. The previous colleague said they had a problem finding some good examples of doing this online, so here’s to hoping that this will help someone when it they need it. Just maybe, I’ll be back with the right way to do this, where the users that are going to be added back, never get removed in the fist place.

Automate App Server (Non Visual) Website Test

This is part II of this post: http://tommymaynard.com/quick-learn-automate-app-server-visual-website-test-2016. It might be beneficial to read that first.

It was minutes before my family needed to leave the house and start our Saturday, when my desire to monitor eight of my application servers hit. In a quick moment, I wrote three commands in three PowerShell consoles and left the house.

Before I show you what was in console one, I have to show and mention a couple functions on which it relied. These functions are a bit of a continuation of the link above. The Watch-MyApp function below, will visually and textually display indicators so that I know if my app servers’ websites are responding, or not. The -Visual parameter will open an instance of Internet Explorer for each server URL, so I can visually check the app servers’ websites (see the link to view this function).  Not using the -Visual parameter, which is how it’s used here, uses Invoke-WebRequest to determine a webpage’s status code. A 200 status code (OK response) means our webpage is up, and responding. If it’s not up, it’ll indicate a timeout warning. It could also produce a different status code, if the page is up, but produces something other than a 200 status code. Other more well-known status codes are the dreaded 404 (page cannot be found), or a 5xx error which indicates a problem with the web server. It may also be helpful to know that the $MyAppServers variable contains the default properties for five servers, returned from the Get-ADComputer cmdlet.

Function Watch-MyApp {
    Param (
        [switch]$Visual
    )
    
    If ($Visual) {
        $MyAppServers.Name | ForEach-Object {
            Open-InternetExplorer -Url "$_.myapp.mydomain.com"
        }
    } Else {
        $MyAppServers.Name | ForEach-Object {
            try {
                $StatusCode = (Invoke-WebRequest -Uri "http://$_.myapp.mydomain.com" -TimeoutSec 10).StatusCode
                "$StatusCode`: $_"
            } catch {
                Write-Warning -Message "$_"
            }    
        }
    }
 }

The first of my three consoles wrapped the function above, Watch-MyApp, in a never ending loop ($true is always true). Every minute it would get the date and time and write it to a log file. Immediately after that, it would run the function to get the status code of each web server, and append it to the same log file. A minute later and it does the same thing. Here’s the command and a sample from the log file it created.

Do {
    (Get-Date).ToString() | Out-File -FilePath 'C:\MyApp.txt' -Append
    Watch-MyApp | Out-File -FilePath 'C:\MyApp.txt' -Append
    Start-Sleep -Seconds 60
} While ($true)
2/21/2016 10:48:29 PM
200: appsrv01
200: appsrv02
200: appsrv03
200: appsrv04
200: appsrv05
2/21/2016 10:49:29 PM
200: appsrv01
200: appsrv02
200: appsrv03
200: appsrv04
200: appsrv05

The second console ran another endless Do-While loop. The difference, is that this loop’s job was to copy the file that was being updated by the command in the first console, to my Dropbox folder every ten minutes. Genius, right? While I could have, I opted to not have my first command write/overwrite the file directly in my Dropbox folder. I didn’t feel it was necessary to update Dropbox each minute for hours on end. When I was back home later, I changed this from ten minutes to 30 minutes, or 1800 seconds, as it was even less important to update Dropbox then.

Do {
    (Get-Date).ToString() | Out-File -FilePath 'C:\MyApp.txt' -Append
    Copy-Item -Path 'C:\MyApp.txt' -Destination 'C:\Users\tommymaynard\Dropbox\MyApp.txt' -Force
    Start-Sleep -Seconds 600
} While ($true)

What this meant is that while I was away, I was able to check Dropbox on my phone and quickly determine if there were any status codes other than a 200. Every ten minutes and there would be an update to my file that would include the status code results for each minute in the last ten.

The final console didn’t do much other than allow me to manually check the file in Dropbox, for something other than a date entry, and status code of 200. So far so good: None of the web apps returned anything more than a 200 status code. Maybe the change at the end of the day on Friday really did fixed things. Here’s the command I used to read the file and exclude lines that had a date and a 200 status code (all the lines unless there was a problem).

Get-Content -Path 'C:\MyApp.txt' | Where-Object {($_ -notlike '*2016*') -and ($_ -notlike '200*')}

But why visually scan the file myself, when PowerShell can do that too!? This third command could’ve also been an endless loop. It could’ve automated scanning the file for me, like I was doing already, and then could’ve been set up to send me an email using the Send-MailMessage cmdlet if it found something, other than the 200 status code. I could’ve avoided looking at the file in Dropbox and just watched my email. One better, and I could’ve had my command do email to text with a specially crafted email address. Here’s what I mean for US Verizon customers: http://www.verizonwireless.com/news/article/2013/06/computer-to-phone-text-messaging.html. Many days, it feels like PowerShell is only limited by what you can think do with it.

There have been times in my life where my wife will ask me, “How would people that don’t know computers, know how to do this?” I usually answer by saying, that “They wouldn’t,” or “I don’t know, I’m not that person.” The same thing applies here: “How would a Windows System Administrator know what to do if they hadn’t already learned PowerShell?” Those three commands might take someone new with PowerShell half a day to consider, and then write. Learn it while you don’t think you need it, because one day you will.

So, to recap, while I was away doing the family thing, my computer sat on the kitchen table at home, checked on eight app servers for me and updated a file in Dropbox, that I was able to check at times while we were away. PowerShell has a purpose, and without it, you’re losing yours.

Automate App Server (Visual) Website Test

To visually determine if a website is loading slowly, or not, you might open the browser, enter the URL, and note the speed at which the page loads. Well, since I write tools to speed things up, writing one for this need, seemed to make sense. Part of the work was already done, thanks to the little function below that I had already written. It’s a function called Open-InternetExplorer and you can likely guess what it does. If you feed it a URL as the value for the -Url parameter, it’ll open up that webpage, otherwise, it’ll just open to the default homepage.

Set-Alias -Name iexplore -Value Open-InternetExplorer
Function Open-InternetExplorer {
    Param ([string]$Url)
 
    If ($Url) {
        Start-Process -FilePath iexplore $Url
    } Else {
        Start-Process -FilePath iexplore
    }
}

If you didn’t already notice, I’ve also included an alias be set for this function. With that in place, I can also enter iexplore to invoke the Open-InternetExplorer function. Notice that the Start-Process cmdlets inside the function include iexplore as the value for the -FilePath parameter. This is the Internet Explorer executable, and not the function alias.

Okay, so now we know how we can quickly open a new IE browser and determine what webpage to load. Let’s assume the page I want to check is http://appsrv01.subdomain.mydomain.com. This means, I can enter Open-InternetExplorer http://appsrv01.subdomain.mydomain.com and IE will open to that webpage (on that server). I may use a load balanced, front end URL for users to access the application, such as http://myapp.subdomain.mydomain.com. While the app is important (as it is one of the servers), knowing that all the app servers are responding and loading the page quickly is helpful.

For the remaining examples, let’s assume I have a variable called $MyAppServers that contains the default properties for five servers, returned from the Get-ADComputer cmdlet.

PS> $MyAppServers = Get-ADComputer -Filter * -SearchBase 'OU=MyApp,DC=subdomain,DC=mydomain,DC=com'
PS> $MyAppServers.Name
appsrv01
appsrv02
appsrv03
appsrv04
appsrv05

Now, let’s combine the $MyAppServers variable, with the function (and the ForEach-Object cmdlet, as the function wasn’t written to accept multiple URLs), so that we can open each app server’s webpage. This will allow us to determine if any of the pages load slower than any others, or more importantly, to determine if a page doesn’t load at all. Here’s how we do that.

PS> $MyAppServers.Name | ForEach-Object {Open-InternetExplorer -Url "$_.subdomain.mydomain.com"}

Once this command is run, it’ll open an Internet Explorer browser window for each app URL. In my case (Windows 8.1), I can hover over the IE taskbar items and view a small image of each IE window. Notice in the image below that one of the application servers didn’t load. I’ll need to look into that server!

automate-app-server-visual-website-test01

While I haven’t yet, the command above could be added to its own function, so I don’t have to type it out each time, and instead can simply enter the function’s name, or alias. In closing, I’ll mention another small function I added to my profile. This one will dump all running instances of Internet Explorer, allowing me to avoid manually closing each IE window, or typing out the Get-Process–Stop-Process command that the function runs.

Set-Alias -Name diexplore -Value Stop-InternetExplorer
Function Stop-InternetExplorer {
    Get-Process -Name iexplore | Stop-Process
}