Tag Archives: Get-Content

Technical Fact at PowerShell Launch

When I read books, or websites, and find worthy facts, I aim to try and keep them. If I read a book, my bookmark is often a few pieces of paper stapled together with info and page numbers from the book. Well, I’m scrapping that technique, which went hand-in-hand with folding down page corners. I’m also ditching the small pieces of paper that litter my desk with random bits of information: “PowerShell objects come from classes,” and “LastLogonDate is the converted version of LastLogonTimeStamp.” Now, it’s all slated to go in a single file called InfoLines.txt in Dropbox, here: C:\Users\tommymaynard\Dropbox\PowerShell\Profile.

If you’re wondering why I use a Dropbox folder it’s because I want this file and its worthy facts to be available regardless of whether I’m on my work, or home computer. You can read more in a post I wrote that uses Dropbox to sync my profile script between work and home, here: http://tommymaynard.com/sync-profile-script-from-work-to-home-2017. It may help make what I’m doing here make more sense.

For now, because I just started this today, I only have a few lines of worthy information. Here’s the contents of my InfoLines.txt file so far. If you can’t tell, I’m finishing up Amazon Web Services in Action. I only have 70 more pages to go!

The auto-scaling group is responsible for connecting a newly launched EC2 instance with the load balancer (ELB). (AWS in Action, p.315)
DevOps is an approach driven by software development to bring development and operations closer together. (AWS in Action, p.93)
Auto-scaling is a part of the EC2 service and helps you to ensure that a specified number of virtual servers are running. (AWS in Action, p.294)

Each time I open the ConsoleHost, the ISE, or Visual Studio Code, I want a random line from the file to be shared with me. The below code will return a single, random line with asterisks both above and below it. This is in order to help separate it from the (totally unnecessary, unwelcome, and shouldn’t even be there) message that tells me how long my “personal and system profiles” took to load, and my prompt. They need to put that message in a variable and not on my screen without permission. Don’t tell us what you think we need to know, guys.

'**************'
Get-Content -Path "$env:USERPROFILE\Dropbox\PowerShell\Profile\InfoLines.txt" | Get-Random
'**************'

That’s it. Now, whenever I open one of these PowerShell hosts, I’ll get a quick reminder about something I found important, and want to keep fresh in my mind.

Update: I decided I wanted the asterisks above and below my technical fact to go from one end of the PowerShell host to other. Here’s how I did that.

'*' * ($Host.UI.RawUI.BufferSize.Width - 1)
Get-Content -Path "$env:USERPROFILE\Dropbox\PowerShell\Profile\InfoLines.txt" | Get-Random
'*' * ($Host.UI.RawUI.BufferSize.Width - 1)

Years Too Late: My First ISE Snippet

Every time I start to write a new PowerShell function, I manually write the same block of text. No idea how many times I’ve done it, but I’ve finally decided to stop. Today, I wrote it for the last time.

$Text = @'
Function ___-_________ {
    [CmdletBinding()]
    Param (
    )

    Begin {
    } # End Begin.

    Process {
    } # End Process.

    End {
    } # End End.
} # End Function: ___-_________.
'@

I’ve known about ISE snippets for some time, but haven’t taken a minute to get my advanced function included. Well, that finally ended today. With the $Text variable assigned above, I ran the following command.

New-IseSnippet -Title BasicFunction -Description 'Basic Advanced Function.' -Text $Text -Author 'Tommy Maynard'

Well, what did this just do? In the most basic reply to that question, it added a new snippet — a reusable chunk of text — I can add to the ISE anytime I want. All I have to do is press Ctrl + J and select BasicFunction from the available options.

That may be all you need, but I was curious what this really did. To find out, I ran Get-IseSnippet and it returned a path — helpful.

Get-IseSnippet

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

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        9/27/2016   0:32 PM            867 BasicFunction.snippets.ps1xml

Then I ran Get-Content on the file to see what it was storing.

Get-Content -Path (Get-IseSnippet).FullName

<?xml version='1.0' encoding='utf-8' ?>
    <Snippets xmlns='http://schemas.microsoft.com/PowerShell/Snippets'>
        <Snippet Version='1.0.0'





<Header>
                <Title>BasicFunction</Title>
                <Description>Basic Advanced Function.</Description>
                <Author>Tommy Maynard</Author>
                <SnippetTypes>
                    <SnippetType>Expansion</SnippetType>
                </SnippetTypes>
            </Header>






            <Code>
                <Script Language='PowerShell' CaretOffset='0'>
                    <![CDATA[Function ___-_________ { [CmdletBinding()] Param ( ) Begin { } # End Begin. Process { } # End Process. End { } # End End. } # End Function: ___-_________.]]>
                </Script>
            </Code>

    </Snippet>
</Snippets>

So, there it is. Not only do I have my basic function snippet the next time I need it, I know that New-IseSnippet is writing an XML (.ps1xml) file to my Snippets directory in my Documents/WindowsPowerShell directory in my local profile. The date on my snippet and the directory indicate they were both created when I ran this command. I told you I hadn’t used snippets before.

Me being me, I ran Get-Command against New-IseSnippet and guess what? It’s a function; it’s not compiled code. Let’s take a look at it; let’s find where it decides whether to create the directory, or not.

Get-Command -Name New-IseSnippet

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        New-IseSnippet                                     1.0.0.0    ISE

(Get-Command -Name New-IseSnippet).ScriptBlock

While I didn’t include the entire results of the last command, I’ll include what’s important for how it determined whether or not to create the Snippets directory. In this first part, the function creates a $snippetPath variable. In it, it stores the current user’s WindowsPowerShell directory path. Before writing that to the variable, Join-Path appends “Snippets” — the child path — to the end. That means that in the end, the $snippetPath variable contains C:\Users\tommymaynard\Documents\WindowsPowerShell\Snippets.

$snippetPath = Join-Path (Split-Path $profile.CurrentUserCurrentHost) "Snippets"

In this section of the function, it runs Test-Path against $snippetPath, to determine if the path exists, or not. This cmdlet returns $true or $false depending on whether the path exists.

if (-not (Test-Path $snippetPath))
{
    $null = mkdir $snippetPath
}

If the path doesn’t exist, thanks to the -not, it executes the mkdir function against the path, and the directory is created. The next time New-IseSnippet function is run, the directory will already exists and this part of the function won’t be run.

Well, that’s it. I’m already looking forward to pressing Ctrl + J the next time I need to start a new advanced function.

Quick Learn – “Get Files” to a Remote Computer

I was a little quiet last week, but vacation can do that. I spent a week in Minneapolis, Minnesota in order to visit my wife’s family. Although away from my computer nearly all that time, PowerShell was still on my mind.

Not long before I left for vacation, I replied to a TechNet forum post. In that post, a user was PS Remoting to a computer, and from that computer trying to connect to another computer to pull down some files. Maybe you’ve been down that path before, or know someone that has.

Typically the user will use Invoke-Command to run commands on a remote computer. One of the commands will attempt to reach out to a network location and download some files. It sounds straight forward enough, but the part they forget, or aren’t aware of, is the inability to delegate your credentials (the user name and password) to the location where the files reside. This inability to delegate credentials to a remote computer, from your already remote connection to a computer, is called the second hop, or double hop, problem. It’s by design, as we don’t want repetitive credential delegation from machine to machine, especially if the credentials are being used maliciously, which is basically what would happen if this delegation were allowed.

When faced with this problem, there are three thoughts that I typically have:

The first though is to copy out the file(s) to the remote computers before the PowerShell Remoting connection. Then, when you need the files on the remote computer, they are already in place. I’ve recommend this several times over the years. Think of this file copy as a prestage for the work your remote command, or commands, will complete using those files.

The second thought is CredSSP, and it should be avoided at all costs whenever possible. Once set up, it allows you to approve the credential delegation from your remote computer to the remote location where you want to get the files. It sounds wonderful, but as of this writing, it is still includes some security concerns.

The third thought is to use an endpoint that runs under a different account. It requires that the endpoint you connect to — that’s the thing that accepts your incoming PowerShell Remoting connection — to run as a user other than the connecting user. When set up, or edited, to do this, the endpoint isn’t running as you, when you connect, and therefore, can delegate its own credentials to the location where your files live. This eliminates the second hop, as the credentials you used to connect to the remote computer aren’t used to get to the location of the file(s) you want to copy down.

Before I left for vacation, I came up with another idea; a fourth thought. When we use Invoke-Command we have the option to take variables with us into the remote session. In a previous post, I showed three examples of how to do this: Read it; it’s short. Now, knowing I can fill a variable with the contents of a file… and knowing I can take a variable into a remote session… did a light just go on?

As I mentioned in the forum post, my fourth thought, was mildly unconventional. It didn’t require someone to prestage (copy the files out, before the remote connection), it wasn’t unsecure (CredSSP), and it didn’t require any knowledge about creating, or editing, endpoints to use a run as account. All it required was reading some files into some variables and taking those variables into the remote PowerShell session. Here’s the example I gave them on TechNet (and here’s the forum thread). I’ll discuss the example, below the example, so you can understand the entire, logical flow.

$ConfigFile01 = Get-Content -Path C:\Config01.txt
$ConfigFile02 = Get-Content -Path C:\Config02.txt

Invoke-Command -ComputerName Server01 -ScriptBlock {
    Add-Content -Path C:\file01.txt -Value $Using:ConfigFile01
    Add-Content -Path C:\file02.txt -Value $Using:ConfigFile02

    $1 = Get-Content -Path C:\file01.txt
    $2 = Get-Content -Path C:\file02.txt

    $1
    $2

    Start-Sleep -Seconds 5

    Remove-Item -Path C:\file01.txt,C:\file02.txt
}

Beginning in the first two lines (1 and 2), I set two variables on my local computer with the content of two different files. The variable $ConfigFile01 stores the content from the file C:\Config01.txt, and $ConfigFile02 stores the content from the file C:\Config02.txt. With these variables set, we run the Invoke-Command command on Server01. Remember, the -ComputerName parameter can accept a comma-separated list of multiple computers.

Inside this script block, we do several things. Keep in mind, as you look this over, that this example isn’t doing much with this code except proving that there’s another way to “get a file to a computer.” The first two lines in the script block (lines 5 and 6), create new files on the remote computer. They create the files C:\file01.txt and C:\file02.txt. The value added to file01.txt comes from the $ConfigFile01 variable, and the value added to C:\file02.txt comes from the $ConfigFile02 variable. At this point, we have our files on the remote computer.

The rest of the script block isn’t necessary, but it helps show some of what we can do. Lines 8 and 9 put the contents of the newly created files on Server01 into variables. Lines 11 and 12 echo the contents of the two variables. On line 14, I pause the script block for five seconds. The reason I did this was so that I could test that the files were created and their contents written before the last line. In that line, line 16, we remove the files we created on the remote computer.

You can test with this example by creating C:\Config01.txt and C:\Config02.txt on your local computer with some text in each. Next, change Server01 in the Invoke-Command to one of your computers. Then, open File Explorer to C$ on the computer to which you’re going to create a PS Remoting session. In my case, I would open it to \\Server01\C$. Having this open will allow you to see the creation, and removal, of file01.txt and file02.txt.

Quick Learn – Proving PowerShell’s Usefulness to Newbies, Part II

Back again with another example to share with the Windows PowerShell newbies. They’re out there: people that don’t know the practical power in PowerShell. The idea is to use real-world examples of things we simply wouldn’t want to do manually, in hopes that our lost friends will find the light. Maybe, that’s you.

Part I can be found here. In that post, we learned that we could use PowerShell to create 10,000 folders in about 10 seconds — that’s 1,000 folders per second, or 1 folder each millisecond.

Part II
What we’ll do today is read in the content of a file, sort the items we’ve read in, and then write our sorted results back out to the same file — it’s instant alphabetizing. Let’s start by reading in our file and see what we’re starting with.

PS> Get-Content -Path .\file.txt
web02.mydomain.com
web09.mydomain.com
dc03.mydomain.com
dc04.mydomain.com
sql08.mydomain.com
sql06.mydomain.com
dc08.mydomain.com
sql04.mydomain.com
dc10.mydomain.com
web01.mydomain.com
web03.mydomain.com
dc09.mydomain.com
web05.mydomain.com
web06.mydomain.com
dc02.mydomain.com
web07.mydomain.com
dc05.mydomain.com
web04.mydomain.com
web10.mydomain.com
sql01.mydomain.com
dc01.mydomain.com
sql03.mydomain.com
sql10.mydomain.com
web08.mydomain.com
sql05.mydomain.com
sql07.mydomain.com
dc07.mydomain.com
sql02.mydomain.com
sql09.mydomain.com
dc06.mydomain.com

Our file contains 30 fully-qualified servers names that are in no significant order. Sorting all 30 of these server names is probably going to be difficult, or at least, it’s going to be time consuming. Wrong. With one quick addition to the previous command — see the example below — our list is sorted and without any noticeable difference in the amount of time it took to complete.

PS> Get-Content -Path .\file.txt | Sort-Object
dc01.mydomain.com
dc02.mydomain.com
dc03.mydomain.com
dc04.mydomain.com
dc05.mydomain.com
dc06.mydomain.com
dc07.mydomain.com
dc08.mydomain.com
dc09.mydomain.com
dc10.mydomain.com
sql01.mydomain.com
sql02.mydomain.com
sql03.mydomain.com
sql04.mydomain.com
sql05.mydomain.com
sql06.mydomain.com
sql07.mydomain.com
sql08.mydomain.com
sql09.mydomain.com
sql10.mydomain.com
web01.mydomain.com
web02.mydomain.com
web03.mydomain.com
web04.mydomain.com
web05.mydomain.com
web06.mydomain.com
web07.mydomain.com
web08.mydomain.com
web09.mydomain.com
web10.mydomain.com

Yeah, that was tough. I ran this same command 10 times, measuring it with the Measure-Command cmdlet, so I could see the average amount of time it took to sort the list of computers. It completed this “challenge” in anywhere between 6 and 10 milliseconds. I’m sorry, but it would’ve taken me at least a few minutes do to this by hand.

Now that we know we can sort it, let’s write it back to disk. In this example, below, we’ll put our sorted results back into the file where it came from, overwriting the original contents. If you don’t want to do that, then be sure to alter the file name value for Set-Content’s -Path parameter.

PS> Get-Content -Path .\file.txt | Sort-Object | Set-Content -Path .\file.txt
PS>

Let’s verify that our file now contains the sorted contents by rerunning our first Get-Content command from the beginning of this post.

PS> Get-Content -Path .\file.txt
dc01.mydomain.com
dc02.mydomain.com
dc03.mydomain.com
dc04.mydomain.com
dc05.mydomain.com
dc06.mydomain.com
dc07.mydomain.com
dc08.mydomain.com
dc09.mydomain.com
dc10.mydomain.com
sql01.mydomain.com
sql02.mydomain.com
sql03.mydomain.com
sql04.mydomain.com
sql05.mydomain.com
sql06.mydomain.com
sql07.mydomain.com
sql08.mydomain.com
sql09.mydomain.com
sql10.mydomain.com
web01.mydomain.com
web02.mydomain.com
web03.mydomain.com
web04.mydomain.com
web05.mydomain.com
web06.mydomain.com
web07.mydomain.com
web08.mydomain.com
web09.mydomain.com
web10.mydomain.com

That’s it. We ran a one-liner command to read in some data, sort it, and push it right back to the same file. So we were able to compare, I went ahead and sorted the same, 30-line file manually. It took me 3 minutes and 30 seconds (proof below). While this wasn’t the most complex, or lengthy document one might sort, the time difference is still quite drastic. PowerShell has plenty to offer, and spending some time to learn it is going to pay off. This line of work requires all the time you have to continue to learn, and stay relevant. PowerShell can give you some time back.

PS C:\> $StartTime = Get-Date
PS C:\> $EndTime = Get-Date
PS C:\> New-TimeSpan -Start $StartTime -End $EndTime


Days              : 0
Hours             : 0
Minutes           : 3
Seconds           : 30
Milliseconds      : 860
Ticks             : 2108600828
TotalDays         : 0.00244051021759259
TotalHours        : 0.0585722452222222
TotalMinutes      : 3.51433471333333
TotalSeconds      : 210.8600828
TotalMilliseconds : 210860.0828

Script Sharing – Copy Outlook Signature to Clipboard

As far as I am aware, the in-house built front end for our help desk ticketing system, doesn’t have a way to include a signature. This means that as I update and close tickets in the office, I often find myself opening a new, blank email, copying my signature, and pasting it in the notes field on the ticket. I know, I know — I’m embarrassed.

No more, am I going to consider this acceptable, especially for someone that uses PowerShell for as many things as I do: it’s. always. open. Today was the day I fixed this forever, and it took a whole two minutes.

I needed to first determine where Outlook (2013 on Windows 8.1) looks for my signature. I traced it down to C:\Users\tommymaynard\AppData\Roaming\Microsoft\Signatures. In that path there are three files named Standard — the same name used for my Signature in Outlook, when I open the Signatures and Stationary dialog. There is a .htm version, a .rtf version, and a .txt version of the signature. Simple decision: I decided I would make use of the text file.

Since I was going to use this in my profile, I didn’t include anything inside the function, but the simple command I wanted to run. Based on the function below, all I need to do is enter Get-Signature, or its alias, and my function will copy the contents of Standard.txt to my clipboard. From there, it’s a quick paste into the help desk ticketing system, and done.

Set-Alias -Name sig -Value Get-Signature
Function Get-Signature {
    $SigPath = 'C:\Users\tommymaynard\AppData\Roaming\Microsoft\Signatures\Standard.txt'
    Get-Content -Path $SigPath | Select-Object -First 4 | clip
}

If you’ve taken a look at the function, you’ll see that I only choose the first 4 lines of the signature file. This was because there was a blank line beneath the last line in my signature, that I wasn’t interested in copying (or manually removing from the file itself).

Twitter Reply – Randomly Selecting a Name from a CSV File

I have to admit, I’m kind of excited to be doing another Twitter Reply post. This one is the result of a post by Michael Bender. I’ve never met him, but I’ve follow his career a bit in the last few years, when I was introduced to his name via TechEd. He made a recent Tweet where he asked for a PowerShell genius to help build him a script. The goal was to randomly select a name from a CSV file, with an option to choose how many names it should randomly select.

Here’s the Tweet: https://twitter.com/MichaelBender/status/593168496571322370

I wouldn’t consider myself a genius, but if you consider what he’s after — a name from a CSV file (not a line from a text file) — then there’s a better option then the one he accepted: “get-content file.csv | get-random.” This will work, but there’s some reasons why this isn’t such a great idea. We can use the Get-Content cmdlet with CSV files, but we generally don’t. CSV files are created and formatted in such a way, that using Get-Content, instead of Import-Csv, isn’t the best way to do things.

Let’s consider that we’re using the CSV file in the image below. It has three headers, or column names: Name, Age, and HairColor.

Randomly Selecting Name from a CSV File01

First of all, if we use Get-Content, it’s going to consider the first line — the header line, or the column headings — a selectable line within the file. Here’s that example:

PS C:\> Get-Content .\file.csv | Get-Random
Name,Age,HairColor

Uh, not helpful. While this isn’t the end of the world, as we can simply rerun the command (and probably get something different), we’d be better off to write this so that this error is not a possibility — something we’ll see in a moment, when we use Import-Csv.

Second of all, unless the only column in the CSV file is Name, then we’re not returning just a name, but instead, everything in a row of the file. This means that if I randomly choose a line in the file, that it will include information that Michael didn’t request — the age and hair color.

PS C:\> Get-Content .\file.csv | Get-Random
Jeff,19,Brown

I suppose we could start parsing our returned value to just return the name ((Get-Content .\file.csv | Get-Random).Split(‘,’)[0]), but seriously, let’s skip that and use one of the cmdlets that was built to work with CSVs directly. We’ll assume we’re still using the same CSV file in the image above.

PS C:\> Import-Csv .\file.csv | Get-Random | Select-Object Name

Name
----
Lance

Um, that was easy. If we wanted to increase the number of names returned from our CSV file, then we would use Get-Random’s -Count parameter and an integer value, such as in this example:

PS C:\> Import-Csv .\file.csv | Get-Random -Count 3 | Select-Object Name

Name
----
Bob
Stephen
Sue

I think we’re best off when we use the *-Csv cmdlets with CSV files, and the Get-Content cmdlet with most other file types we can read in PowerShell. There is at least one exception of doing this in reverse: Using Import-Csv with a text file, that has a delimiter, might help you better parse the information in your file. Consider this text file:

Randomly Selecting Name from a CSV File02

The next few examples will progressively show you how to use Import-Csv with a delimited text file until all of the “columns” are broken apart. When we’re finished, we can even export our data into a properly formatted CSV file, and then import it, piping those results to other cmdlets — take a look.

PS C:\> Import-Csv .\file.txt

001|Sunday|Car7
---------------
002|Monday|Car2
003|Tuesday|Car3
004|Wednesday|Car3
005|Thursday|Car7
006|Friday|Car2
007|Saturday|Car3

PS C:\> Import-Csv .\file.txt -Header Id,Day,Car

Id                                      Day                                     Car
--                                      ---                                     ---
001|Sunday|Car7
002|Monday|Car2
003|Tuesday|Car3
004|Wednesday|Car3
005|Thursday|Car7
006|Friday|Car2
007|Saturday|Car3

PS C:\> Import-Csv .\file.txt -Header Id,Day,Car -Delimiter '|'

Id                                      Day                                     Car
--                                      ---                                     ---
001                                     Sunday                                  Car7
002                                     Monday                                  Car2
003                                     Tuesday                                 Car3
004                                     Wednesday                               Car3
005                                     Thursday                                Car7
006                                     Friday                                  Car2
007                                     Saturday                                Car3

PS C:\> Import-Csv .\file.txt -Header Id,Day,Car -Delimiter '|' | Export-Csv -Path C:\file-cars.csv -NoTypeInformation
PS C:\> Import-Csv .\file-cars.csv

Id                                      Day                                     Car
--                                      ---                                     ---
001                                     Sunday                                  Car7
002                                     Monday                                  Car2
003                                     Tuesday                                 Car3
004                                     Wednesday                               Car3
005                                     Thursday                                Car7
006                                     Friday                                  Car2
007                                     Saturday                                Car3

PS C:\> Import-Csv .\file-cars.csv | Where-Object Car -eq 'Car3'

Id                                      Day                                     Car
--                                      ---                                     ---
003                                     Tuesday                                 Car3
004                                     Wednesday                               Car3
007                                     Saturday                                Car3

PS C:\> Import-Csv .\file-cars.csv | Where-Object Car -eq 'Car3' | Select-Object Day

Day
---
Tuesday
Wednesday
Saturday

So, if I had seen the Tweet first, I would have to recommend Import-Csv, piped to Get-Random, and then piped to Select-Object Name. Thanks for reading the post.

Quick Learn – What PowerShell Modules does the RSAT Provide (on Windows 8.1)

I built a new Windows 8.1 machine recently, and as we all know, doing that requires new software installations. In my case, that included the RSAT. The RSAT, or Remote Server Administration Tools, allow IT admins the ability to remotely manage features on Windows Server operating systems from a client operating system—here’s a link to the RSAT for Windows 8.1.

Prior to running the RSAT installer, I wanted to collect the currently available Windows PowerShell modules that I already had on my computer. This would allow me to know exactly what modules were added after the RSAT installer finished. The command below collects all the modules, returns only their name, and then drops that into a file on my computer. We’ll use the file in a moment, as part of a comparison.

PS C:\> Get-Module -ListAvailable | Select-Object -ExpandProperty Name | Out-File C:\Users\tommymaynard\Pre-RSAT-PowerShell-Modules.txt

I verified the file contained the module names by using the Get-Content cmdlet. In addition, I ran a slightly modified command to get the item count in the file—both can be seen below. The output created by the first command has been truncated to save space.

PS C:\> Get-Content C:\Users\tommymaynard\Pre-RSAT-PowerShell-Modules.txt
AppBackgroundTask
AppLocker
Appx
AssignedAccess
...
PS C:\> (Get-Content C:\Users\tommymaynard\Pre-RSAT-PowerShell-Modules.txt).Count
57

Once this file was in placed, and I was satisfied that it contained what I wanted, I went ahead with the RSAT install. Upon completion, I reran the Get-Module command above, after modifying the name of the file it would create (pre vs. post). I then read in the contents of the new file (which has been truncated again), and checked the number of modules listed in the file. There were now 75 modules where there had only been 57 before the RSAT install.

PS C:\> Get-Module -ListAvailable | Select-Object -ExpandProperty Name | Out-File C:\Users\tommymaynard\Post-RSAT-PowerShell-Modules.txt
PS C:\> Get-Content C:\Users\tommymaynard\Post-RSAT-PowerShell-Modules.txt
ActiveDirectory
AppBackgroundTask
AppLocker
Appx
...
PS C:\> (Get-Content C:\Users\tommymaynard\Post-RSAT-PowerShell-Modules.txt).Count
75

Knowing that there’s 18 new modules is helpful, but which ones were added? The Compare-Object cmdlet can tell us. As seen below, we have the cmdlet read in the lines of each file and then determine which ones aren’t included in both files.

PS C:\> Compare-Object -ReferenceObject (Get-Content C:\Users\tommymaynard\Desktop\Pre-RSAT-PowerShell-Modules.txt) -DifferenceObject (Get-Content C:\Users\tommymaynard\Desktop\Post-RSAT-PowerShell-Modules.txt)

InputObject                                                 SideIndicator
-----------                                                 -------------
ActiveDirectory                                             =>
BestPractices                                               =>
ClusterAwareUpdating                                        =>
DFSN                                                        =>
DFSR                                                        =>
DhcpServer                                                  =>
DnsServer                                                   =>
FailoverClusters                                            =>
GroupPolicy                                                 =>
IpamServer                                                  =>
IscsiTarget                                                 =>
NetworkLoadBalancingClusters                                =>
NFS                                                         =>
RemoteAccess                                                =>
RemoteDesktop                                               =>
ServerManager                                               =>
ServerManagerTasks                                          =>
UpdateServices                                              =>

As we can see above, our Compare-Object results indicate that the difference object (the post file, or the file on the right) has new modules—now we know which ones were added since installing the RSAT.

Help Rewrite – about_Aliases

This post is the help rewrite for about_Aliases. 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)

An Alias in Windows PowerShell is a simplified, or quicker, way to type a cmdlet using an alternate name. Get-Alias (or the alias for Get-Alias, gal) will display a list of all of the aliases that the Windows PowerShell session knows about. This includes both built-in aliases and any additional aliases created or imported. The first two examples below, indicate two ways to accomplish the same thing – listing all the aliases. These examples only show the first four results.

PS C:\> Get-Alias

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           % -> ForEach-Object
Alias           ? -> Where-Object
Alias           ac -> Add-Content
Alias           asnp -> Add-PSSnapin

This example uses the alias for the Get-Alias cmdlet, gal.

PS C:\> gal

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           % -> ForEach-Object
Alias           ? -> Where-Object
Alias           ac -> Add-Content
Alias           asnp -> Add-PSSnapin

In order to find the cmdlet associated with a single alias, the alias needs to be provided, as the value for the -Name parameter, to the Get-Alias cmdlet.

PS C:\> gal -Name gc

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           gc -> Get-Content

The name parameter (-Name) is not required to use it. This means that if there is something after the Get-Alias cmdlet, such as gc in this example, then it will default to using the -Name parameter.

PS C:\> gal gc

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           gc -> Get-Content

Windows PowerShell will error if the -Name parameter is supplied with a cmdlet name, or another value that is not an alias.

PS C:\> gal Get-Content

gal : This command cannot find a matching alias because an alias with the name ‘Get-Content’ does not exist.
At line:1 char:1
+ gal Get-Content
+ ~~~~~~~~~~~~~~~
+ CategoryInfo          : ObjectNotFound: (Get-Content:String) [Get-Alias], ItemNotFoundException
+ FullyQualifiedErrorId : ItemNotFoundException,Microsoft.PowerShell.Commands.GetAliasCommand

In order to get an alias (or aliases, if there is more than one) for a cmdlet, the -Definition parameter must be used.

PS C:\> gal -Definition Get-Content

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           cat -> Get-Content
Alias           gc -> Get-Content
Alias           type -> Get-Content

The Get-Service cmdlet returns the computer’s services, the Get-Process cmdlet return the processes running on the computer, and the Get-ChildItem cmdlet returns the directories and/or files from the root of a drive or from a folder. Here is how a user can get the aliases for multiple cmdlets at the same time.

PS C:\> gal -Definition Get-Service,Get-Process,Get-ChildItem

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           gsv -> Get-Service
Alias           gps -> Get-Process
Alias           ps -> Get-Process
Alias           dir -> Get-ChildItem
Alias           gci -> Get-ChildItem
Alias           ls -> Get-ChildItem

There are a few other cmdlets that allow a user to work with aliases. By using the Get-Command cmdlet (or its alias – if it has one), additional cmdlets can be returned that all end with -Alias.

PS C:\> Get-Command *-Alias

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Export-Alias                                       Microsoft.PowerShell.Utility
Cmdlet          Get-Alias                                          Microsoft.PowerShell.Utility
Cmdlet          Import-Alias                                       Microsoft.PowerShell.Utility
Cmdlet          New-Alias                                          Microsoft.PowerShell.Utility
Cmdlet          Set-Alias                                          Microsoft.PowerShell.Utility

Export-Alias: Exports information about currently defined aliases to a file.

PS C:\> Export-Alias -Path 'C:\aliases.txt'

Import-Alias: Imports an alias, or aliases, from a file.

PS C:\> Import-Alias -Path 'C:\ImportedAliases.txt'

Trying to import aliases that already exists will cause an error for every alias Windows PowerShell tries to import (that already exists).

PS C:\> Export-Alias -Path 'C:\aliases.txt'
PS C:\> Import-Alias -Path 'C:\aliases.txt'
Import-Alias : The alias is not allowed, because an alias with the name ‘ac’ already exists.
At line:1 char:1
+ Import-Alias -Path ‘C:\aliases.txt’
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceExists: (ac:String) [Import-Alias], SessionStateException
    + FullyQualifiedErrorId : AliasAlreadyExists,Microsoft.PowerShell.Commands.ImportAliasCommand

New-Alias: Creates a new alias.
Set-Alias: Changes an existing alias, or creates an alias if it does not already exist.

PS C:\> New-Alias -Name MyAlias -Value Get-Process
PS C:\> MyAlias | select -First 4

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    224      19     3440        772   110    16.50   4612 ALMon
    164      14     2476       2108    44     5.36   2744 ALsvc
     77       9     1336       5288    75   137.55   4076 ApMsgFwd
     90       8     1372       5788    76   162.11   4324 ApntEx

PS C:\> Set-Alias -Name MyAlias -Value Get-Service
PS C:\> MyAlias | select -First 4

Status   Name               DisplayName
------   ----               -----------
Running  AdobeARMservice    Adobe Acrobat Update Service
Stopped  AdobeFlashPlaye... Adobe Flash Player Update Service
Stopped  AeLookupSvc        Application Experience
Stopped  ALG                Application Layer Gateway Service

Bonus Information

Use the Measure-Object cmdlet, or the count property, to find out how many aliases Windows PowerShell knows about.

PS C:\> Get-Alias | Measure-Object

Count    : 182
Average  :
Sum      :
Maximum  :
Minimum  :
Property :

PS C:\> gal | measure

Count    : 182
Average  :
Sum      :
Maximum  :
Minimum  :
Property :

PS C:\> (gal | measure).count
182
PS C:\> (gal).count
182

Real World

While aliases are helpful in the console, the belief is that they should not be used in a script file (.ps1 file). Using full cmdlet names in a script is preferred for script readability. There are plenty of people writing Windows PowerShell who adhere to this best practice even while using aliases for the object cmdlets (select for Select-Object, where for Where-Object, etc.).

Learn More

This information, and more, is stored in the help file about_Aliases 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_aliases
PS C:\> Get-Help about_aliases -ShowWindow
PS C:\> Get-Help about_aliases | clip
PS C:\> Notepad C:\Windows\System32\WindowsPowerShell\v1.0\en-US\about_Aliases.help.txt

There is a built-in, automatic variable, $PSHOME, that stores the installation path of Windows PowerShell. This means that the third example above could have been partially written using that variable.

PS C:\> Notepad $PSHOME\en-us\about_Aliases.help.txt